diff --git a/.gitignore b/.gitignore index e1abcaa639..b68d13cdef 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ prebuilt ## doxygen Doxygen +!docs/cs-manual/Makefile ## ides/editors *~ @@ -21,6 +22,7 @@ Doxygen .project .settings .directory +.idea ## qt-creator CMakeLists.txt.user* @@ -38,10 +40,33 @@ resources /openmw /opencs /niftest +bsatool +openmw-cs +openmw-essimporter +openmw-iniimporter +openmw-launcher +openmw-wizard ## generated objects apps/openmw/config.hpp +apps/launcher/ui_contentselector.h +apps/launcher/ui_settingspage.h +apps/opencs/ui_contentselector.h +apps/opencs/ui_filedialog.h +apps/wizard/qrc_wizard.cxx +apps/wizard/ui_componentselectionpage.h +apps/wizard/ui_conclusionpage.h +apps/wizard/ui_existinginstallationpage.h +apps/wizard/ui_importpage.h +apps/wizard/ui_installationpage.h +apps/wizard/ui_installationtargetpage.h +apps/wizard/ui_intropage.h +apps/wizard/ui_languageselectionpage.h +apps/wizard/ui_methodselectionpage.h +components/ui_contentselector.h docs/mainpage.hpp +docs/Doxyfile +docs/DoxyfilePages moc_*.cxx *.cxx_parameters *qrc_launcher.cxx @@ -53,3 +78,6 @@ moc_*.cxx *ui_playpage.h *.[ao] *.so +gamecontrollerdb.txt +openmw.appdata.xml + diff --git a/.travis.yml b/.travis.yml index 0910ac5462..839dbccd4a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,10 @@ os: - linux -# - osx + - osx +osx_image: xcode7.2 language: cpp +sudo: required +dist: trusty branches: only: - master @@ -18,8 +21,8 @@ addons: name: "OpenMW/openmw" description: "" notification_email: scrawl@baseoftrash.de - build_command_prepend: "cmake . -DBUILD_UNITTESTS=FALSE -DBUILD_OPENCS=FALSE" - build_command: "make" + build_command_prepend: "cmake . -DBUILD_UNITTESTS=FALSE" + build_command: "make -j2" branch_pattern: coverity_scan matrix: include: @@ -38,7 +41,7 @@ before_script: - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then ./CI/before_script.osx.sh; fi script: - cd ./build - - if [ "$COVERITY_SCAN_BRANCH" != 1 ]; then ${ANALYZE}make -j2; fi + - if [ "$COVERITY_SCAN_BRANCH" != 1 ]; then ${ANALYZE}make -j3; fi - if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi - if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./openmw_test_suite; fi - if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi diff --git a/AUTHORS.md b/AUTHORS.md index 292c1e9e3d..b97642aba1 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -14,15 +14,20 @@ Programmers Adam Hogan (aurix) Aesylwinn + aegis Aleksandar Jovanov Alex Haddad (rainChu) Alex McKibben (WeirdSexy) + alexanderkjall Alexander Nadeau (wareya) Alexander Olofsson (Ace) + Allofich + Austin Salgat (Salgat) Artem Kotsynyak (greye) artemutin Arthur Moore (EmperorArthur) athile + Ben Shealy (bentsherman) Bret Curtis (psi29a) Britt Mathis (galdor557) cc9cii @@ -48,6 +53,7 @@ Programmers Gašper Sedej gugus/gus Hallfaer Tuilinn + hristoast Internecine Jacob Essex (Yacoby) Jannik Heller (scrawl) @@ -56,6 +62,7 @@ Programmers Jeffrey Haines (Jyby) Jengerer Jiří Kuneš (kunesj) + Joe Wilkerson (neuralroberts) Joel Graff (graffy) John Blomberg (fstp) Jordan Ayers @@ -81,6 +88,7 @@ Programmers Michael Mc Donnell Michael Papageorgiou (werdanith) Michał Bień (Glorf) + Michał Moroz (dragonee) Miroslav Puda (pakanek) MiroslavR naclander @@ -92,15 +100,17 @@ Programmers Nolan Poe (nopoe) Paul Cercueil (pcercuei) Paul McElroy (Greendogo) + Pi03k Pieter van der Kloet (pvdk) pkubik Radu-Marius Popovici (rpopovici) rdimesio riothamus + Rob Cutmore (rcutmore) Robert MacGregor (Ragora) Rohit Nirmal Roman Melnik (Kromgart) - Roman Proskuryakov (humbug) + Roman Proskuryakov (kpp) Sandy Carter (bwrsandman) Scott Howard Sebastian Wick (swick) @@ -109,7 +119,9 @@ Programmers smbas Stefan Galowicz (bogglez) Stanislav Bobrov (Jiub) + svaante Sylvain Thesnieres (Garvek) + t6 terrorfisch Thomas Luppi (Digmaster) Tom Mason (wheybags) @@ -124,6 +136,7 @@ Manual Bodillium Cramal + Alejandro Sanchez (HiPhish) sir_herrbatka Packagers diff --git a/CHANGELOG.md b/CHANGELOG.md index 43e598566c..6d427fa82e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,162 @@ +0.39.0 +------ + + Bug #1384: Dark Brotherhood Assassin (and other scripted NPCs?) spawns beneath/inside solid objects + Bug #1544: "Drop" drops equipped item in a separate stack + Bug #1587: Collision detection glitches + Bug #1629: Container UI locks up in Vivec at Jeanne's + Bug #1771: Dark Brotherhood Assassin oddity in Eight Plates + Bug #1827: Unhandled NiTextureEffect in ex_dwrv_ruin30.nif + Bug #2089: When saving while swimming in water in an interior cell, you will be spawned under water on loading + Bug #2295: Internal texture not showing, nipixeldata + Bug #2363: Corpses don't disappear + Bug #2369: Respawns should be timed individually + Bug #2393: Сharacter is stuck in the tree + Bug #2444: [Mod] NPCs from Animated Morrowind appears not using proper animations + Bug #2467: Creatures do not respawn + Bug #2515: Ghosts in Ibar-Dad spawn stuck in walls + Bug #2610: FixMe script still needs to be implemented + Bug #2689: Riekling raider pig constantly screams while running + Bug #2719: Vivec don't put their hands on the knees with this replacer (Psymoniser Vivec God Replacement NPC Edition v1.0 + Bug #2737: Camera shaking when side stepping around object + Bug #2760: AI Combat Priority Problem - Use of restoration spell instead of attacking + Bug #2806: Stack overflow in LocalScripts::getNext + Bug #2807: Collision detection allows player to become stuck inside objects + Bug #2814: Stairs to Marandus have improper collision + Bug #2925: Ranes Ienith will not appear, breaking the Morag Tong and Thieves Guid questlines + Bug #3024: Editor: Creator bar in startscript subview does not accept script ID drops + Bug #3046: Sleep creature: Velk is spawned half-underground in the Thirr River Valley + Bug #3080: Calling aifollow without operant in local script every frame causes mechanics to overheat + log + Bug #3101: Regression: White guar does not move + Bug #3108: Game Freeze after Killing Diseased Rat in Foreign Quarter Tomb + Bug #3124: Bloodmoon Quest - Rite of the Wolf Giver (BM_WolfGiver) – Innocent victim won't turn werewolf + Bug #3125: Improper dialogue window behavior when talking to creatures + Bug #3130: Some wandering NPCs disappearing, cannot finish quests + Bug #3132: Editor: GMST ID named sMake Enchantment is instead named sMake when making new game from scratch + Bug #3133: OpenMW and the OpenCS are writting warnings about scripts that use the function GetDisabled. + Bug #3135: Journal entry for The Pigrim's Path missing name + Bug #3136: Dropped bow is displaced + Bug #3140: Editor: OpenMW-CS fails to open newly converted and saved omwaddon file. + Bug #3142: Duplicate Resist Magic message + Bug #3143: Azura missing her head + Bug #3146: Potion effect showing when ingredient effects are not known + Bug #3155: When executing chop attack with a spear, hands turn partly invisible + Bug #3161: Fast travel from Silt Strider or Boat Ride will break save files made afterwards + Bug #3163: Editor: Objects dropped to scene do not always save + Bug #3173: Game Crashes After Casting Recall Spell + Bug #3174: Constant effect enchantments play spell animation on dead bodies + Bug #3175: Spell effects do not wear down when caster dies + Bug #3176: NPCs appearing randomly far away from towns + Bug #3177: Submerged corpse floats ontop of water when it shouldn't (Widow Vabdas' Deed quest) + Bug #3184: Bacola Closcius in Balmora, South Wall Cornerclub spams magic effects if attacked + Bug #3207: Editor: New objects do not render + Bug #3212: Arrow of Ranged Silence + Bug #3213: Looking at Floor After Magical Transport + Bug #3220: The number of remaining ingredients in the alchemy window doesn't go down when failing to brew a potion + Bug #3222: Falling through the water in Vivec + Bug #3223: Crash at the beginning with MOD (The Symphony) + Bug #3228: Purple screen when leveling up. + Bug #3233: Infinite disposition via MWDialogue::Filter::testDisposition() glitch + Bug #3234: Armor mesh stuck on body in inventory menu + Bug #3235: Unlike vanilla, OpenMW don't allow statics and activators cast effects on the player. + Bug #3238: Not loading cells when using Poorly Placed Object Fix.esm + Bug #3248: Editor: Using the "Next Script" and "Previous Script" buttons changes the record status to "Modified" + Bug #3258: Woman biped skeleton + Bug #3259: No alternating punches + Bug #3262: Crash in class selection menu + Bug #3279: Load menu: Deleting a savegame makes scroll bar jump to the top + Bug #3326: Starting a new game, getting to class selection, then starting another new game temporarily assigns Acrobat class + Bug #3327: Stuck in table after loading when character was sneaking when quicksave + Feature #652: Editor: GMST verifier + Feature #929: Editor: Info record verifier + Feature #1279: Editor: Render cell border markers + Feature #2482: Background cell loading and caching of loaded cells + Feature #2484: Editor: point lighting + Feature #2801: Support NIF bump map textures in osg + Feature #2926: Editor: Optional line wrap in script editor wrap lines + Feature #3000: Editor: Reimplement 3D scene camera system + Feature #3035: Editor: Make scenes a drop target for referenceables + Feature #3043: Editor: Render cell markers v2 + Feature #3164: Editor: Instance Selection Menu + Feature #3165: Editor: Instance editing mode - move sub mode + Feature #3244: Allow changing water Level of Interiors behaving like exteriors + Feature #3250: Editor: Use "Enter" key instead of clicking "Create" button to confirm ID input in Creator Bar + Support #3179: Fatal error on startup + +0.38.0 +------ + + Bug #1699: Guard will continuously run into mudcrab + Bug #1934: Saw in Dome of Kasia doesnt harm the player + Bug #1962: Rat floats when killed near the door + Bug #1963: Kwama eggsacks pulse too fast + Bug #2198: NPC voice sound source should be placed at their head + Bug #2210: OpenMW installation wizard crashes... + Bug #2211: Editor: handle DELE subrecord at the end of a record + Bug #2413: ESM error Unknown subrecord in Grandmaster of Hlaalu + Bug #2537: Bloodmoon quest Ristaag: Sattir not consistently dying, plot fails to advance; same with Grerid + Bug #2697: "The Swimmer" moves away after leading you to underwater cave + Bug #2724: Loading previous save duplicates containers and harvestables + Bug #2769: Inventory doll - Cursor not respecting order of clothes + Bug #2865: Scripts silently fail when moving NPCs between cells. + Bug #2873: Starting a new game leads to CTD / Fatal Error + Bug #2918: Editor: it's not possible to create an omwaddon containing a dot in the file name + Bug #2933: Dialog box can't disable a npc if it is in another cell. (Rescue Madura Seran). + Bug #2942: atronach sign behavior (spell absorption) changes when trying to receive a blessing at "shrine of tribunal" + Bug #2952: Enchantment Merchant Items reshuffled EVERY time 'barter' is clicked + Bug #2961: ESM Error: Unknown subrecord if Deus Ex Machina mod is loaded + Bug #2972: Resurrecting the player via console does not work when health was 0 + Bug #2986: Projectile weapons work underwater + Bug #2988: "Expected subrecord" bugs showing up. + Bug #2991: Can't use keywords in strings for MessageBox + Bug #2993: Tribunal:The Shrine of the Dead – Urvel Dulni can't stop to follow the player. + Bug #3008: NIFFile Error while loading meshes with a NiLODNode + Bug #3010: Engine: items should sink to the ground when dropped under water + Bug #3011: NIFFile Error while loading meshes with a NiPointLight + Bug #3016: Engine: something wrong with scripting - crash / fatal error + Bug #3020: Editor: verify does not check if given "item ID" (as content) for a "container" exists + Bug #3026: [MOD: Julan Ashlander Companion] Dialogue not triggering correctly + Bug #3028: Tooltips for Health, Magicka and Fatigue show in Options menu even when bars aren't visible + Bug #3034: Item count check dialogue option doesn't work (Guards accept gold even if you don't have enough) + Bug #3036: Owned tooltip color affects spell tooltips incorrrectly + Bug #3037: Fatal error loading old ES_Landscape.esp in Store::search + Bug #3038: Player sounds come from underneath + Bug #3040: Execution of script failed: There is a message box already + Bug #3047: [MOD: Julan Ashlander Companion] Scripts KS_Bedscript or KS_JulanNight not working as intended + Bug #3048: Fatal Error + Bug #3051: High field of view results in first person rendering glitches + Bug #3053: Crash on new game at character class selection + Bug #3058: Physiched sleeves aren't rendered correctly. + Bug #3060: NPCs use wrong landing sound + Bug #3062: Mod support regression: Andromeda's fast travel. + Bug #3063: Missing Journal Textures without Tribunal and Bloodmoon installed + Bug #3077: repeated aifollow causes the distance to stack + Bug #3078: Creature Dialogues not showing when certain Function/Conditions are required. + Bug #3082: Crash when entering Holamayan Monastery with mesh replacer installed + Bug #3086: Party at Boro's House – Creature with Class don't talk under OpenMW + Bug #3089: Dreamers spawn too soon + Bug #3100: Certain controls erroneously work as a werewolf + Bug #3102: Multiple unique soultrap spell sources clone souls. + Bug #3105: Summoned creatures and objects disappear at midnight + Bug #3112: gamecontrollerdb file creation with wrong extension + Bug #3116: Dialogue Function "Same Race" is avoided + Bug #3117: Dialogue Bug: Choice conditions are tested when not in a choice + Bug #3118: Body Parts are not rendered when used in a pose. + Bug #3122: NPC direction is reversed during sneak awareness check + Feature #776: Sound effects from one direction don't necessarily affect both speakers in stereo + Feature #858: Different fov settings for hands and the game world + Feature #1176: Handle movement of objects between cells + Feature #2507: Editor: choosing colors for syntax highlighting + Feature #2867: Editor: hide script error list when there are no errors + Feature #2885: Accept a file format other than nif + Feature #2982: player->SetDelete 1 results in: PC can't move, menu can be opened + Feature #2996: Editor: make it possible to preset the height of the script check area in a script view + Feature #3014: Editor: Tooltips in 3D scene + Feature #3064: Werewolf field of view + Feature #3074: Quicksave indicator + Task #287: const version of Ptr + Task #2542: Editor: redo user settings system + 0.37.0 ------ diff --git a/CI/before_install.linux.sh b/CI/before_install.linux.sh index 6e288aa149..1c02bc8d99 100755 --- a/CI/before_install.linux.sh +++ b/CI/before_install.linux.sh @@ -1,22 +1,17 @@ #!/bin/sh if [ "${ANALYZE}" ]; then - if [ $(lsb_release -sc) = "precise" ]; then - echo "yes" | sudo apt-add-repository ppa:ubuntu-toolchain-r/test - fi echo "yes" | sudo add-apt-repository "deb http://llvm.org/apt/`lsb_release -sc`/ llvm-toolchain-`lsb_release -sc`-3.6 main" wget -O - http://llvm.org/apt/llvm-snapshot.gpg.key|sudo apt-key add - fi echo "yes" | sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu `lsb_release -sc` main universe restricted multiverse" echo "yes" | sudo apt-add-repository ppa:openmw/openmw -echo "yes" | sudo apt-add-repository ppa:boost-latest/ppa sudo apt-get update -qq sudo apt-get install -qq libgtest-dev google-mock -sudo apt-get install -qq libboost-filesystem1.55-dev libboost-program-options1.55-dev libboost-system1.55-dev libboost-thread1.55-dev -sudo apt-get install -qq ffmpeg libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev +sudo apt-get install -qq libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-thread-dev +sudo apt-get install -qq libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev sudo apt-get install -qq libbullet-dev libopenscenegraph-dev libmygui-dev libsdl2-dev libunshield-dev libtinyxml-dev libopenal-dev libqt4-dev -sudo apt-get install -qq cmake-data #workaround for broken osgqt cmake script in ubuntu 12.04 if [ "${ANALYZE}" ]; then sudo apt-get install -qq clang-3.6; fi sudo mkdir /usr/src/gtest/build cd /usr/src/gtest/build diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index 8bfe2b70f3..b9da521938 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -1,9 +1,10 @@ #!/bin/sh -export CXX=clang++ -export CC=clang - -brew tap openmw/openmw brew update -brew unlink boost -brew install openmw-mygui openmw-bullet openmw-sdl2 openmw-ffmpeg openmw/openmw/qt unshield +brew rm cmake || true +brew rm pkgconfig || true +brew rm qt5 || true +brew install cmake pkgconfig qt5 + +curl http://downloads.openmw.org/osx/dependencies/openmw-deps-263d4a8.zip -o ~/openmw-deps.zip +unzip ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index 71ddd20407..93be1cb48e 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -1,7 +1,8 @@ #!/bin/sh +free -m mkdir build cd build export CODE_COVERAGE=1 if [ "${CC}" = "clang" ]; then export CODE_COVERAGE=0; fi -${ANALYZE}cmake .. -DBUILD_WITH_CODE_COVERAGE=${CODE_COVERAGE} -DBUILD_UNITTESTS=1 -DCMAKE_INSTALL_PREFIX=/usr -DBINDIR=/usr/games -DCMAKE_BUILD_TYPE="RelWithDebInfo" -DUSE_SYSTEM_TINYXML=TRUE +${ANALYZE}cmake .. -DBUILD_WITH_CODE_COVERAGE=${CODE_COVERAGE} -DBUILD_UNITTESTS=1 -DCMAKE_INSTALL_PREFIX=/usr -DBINDIR=/usr/games -DCMAKE_BUILD_TYPE="None" -DUSE_SYSTEM_TINYXML=TRUE diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh new file mode 100644 index 0000000000..c0ac12d049 --- /dev/null +++ b/CI/before_script.msvc.sh @@ -0,0 +1,671 @@ +#!/bin/bash + +while [ $# -gt 0 ]; do + ARGSTR=$1 + shift + + if [ ${ARGSTR:0:1} != "-" ]; then + echo "Unknown argument $ARGSTR" + echo "Try '$0 -h'" + exit 1 + fi + + for (( i=1; i<${#ARGSTR}; i++ )); do + ARG=${ARGSTR:$i:1} + case $ARG in + V ) + VERBOSE=true ;; + + v ) + VS_VERSION=$1 + shift ;; + + d ) + SKIP_DOWNLOAD=true ;; + + e ) + SKIP_EXTRACT=true ;; + + k ) + KEEP=true ;; + + u ) + UNITY_BUILD=true ;; + + p ) + PLATFORM=$1 + shift ;; + + c ) + CONFIGURATION=$1 + shift ;; + + h ) + cat < + Set the configuration, can also be set with environment variable CONFIGURATION. + -d + Skip checking the downloads. + -e + Skip extracting dependencies. + -h + Show this message. + -k + Keep the old build directory, default is to delete it. + -p + Set the build platform, can also be set with environment variable PLATFORM. + -u + Configure for unity builds. + -v <2013/2015> + Choose the Visual Studio version to use. + -V + Run verbosely +EOF + exit 0 + ;; + + * ) + echo "Unknown argument $ARG." + echo "Try '$0 -h'" + exit 1 ;; + esac + done +done + +if [ -z $VERBOSE ]; then + STRIP="> /dev/null 2>&1" +fi +if [ -z $VS_VERSION ]; then + VS_VERSION="2013" +fi + +if [ -z $APPVEYOR ]; then + echo "Running prebuild outside of Appveyor." + + DIR=$(echo "$0" | sed "s,\\\\,/,g" | sed "s,\(.\):,/\\1,") + cd $(dirname "$DIR")/.. +else + echo "Running prebuild in Appveyor." + + cd $APPVEYOR_BUILD_FOLDER + VERSION="$(cat README.md | grep Version: | awk '{ print $3; }')-$(git rev-parse --short HEAD)" + appveyor UpdateBuild -Version "$VERSION" > /dev/null & +fi + +run_cmd() { + CMD="$1" + shift + + if [ -z $VERBOSE ]; then + eval $CMD $@ > output.log 2>&1 + RET=$? + + if [ $RET -ne 0 ]; then + if [ -z $APPVEYOR ]; then + echo "Command $CMD failed, output can be found in `real_pwd`/output.log" + else + echo + echo "Command $CMD failed;" + cat output.log + fi + else + rm output.log + fi + + return $RET + else + eval $CMD $@ + return $? + fi +} + +download() { + if [ $# -lt 3 ]; then + echo "Invalid parameters to download." + return 1 + fi + + NAME=$1 + shift + + echo "$NAME..." + + while [ $# -gt 1 ]; do + URL=$1 + FILE=$2 + shift + shift + + if ! [ -f $FILE ]; then + printf " Downloading $FILE... " + + if [ -z $VERBOSE ]; then + curl --silent --retry 10 -kLy 5 -o $FILE $URL + RET=$? + else + curl --retry 10 -kLy 5 -o $FILE $URL + RET=$? + fi + + if [ $RET -ne 0 ]; then + echo "Failed!" + else + echo "Done." + fi + else + echo " $FILE exists, skipping." + fi + done + + if [ $# -ne 0 ]; then + echo "Missing parameter." + fi +} + +real_pwd() { + pwd | sed "s,/\(.\),\1:," +} + +CMAKE_OPTS="" +add_cmake_opts() { + CMAKE_OPTS="$CMAKE_OPTS $@" +} + +RUNTIME_DLLS="" +add_runtime_dlls() { + RUNTIME_DLLS="$RUNTIME_DLLS $@" +} + +OSG_PLUGINS="" +add_osg_dlls() { + OSG_PLUGINS="$OSG_PLUGINS $@" +} + +if [ -z $PLATFORM ]; then + PLATFORM=`uname -m` +fi + +if [ -z $CONFIGURATION ]; then + CONFIGURATION="Debug" +fi + +case $VS_VERSION in + 14|2015 ) + GENERATOR="Visual Studio 14 2015" + XP_TOOLSET="v140_xp" + ;; + +# 12|2013| + * ) + GENERATOR="Visual Studio 12 2013" + XP_TOOLSET="v120_xp" + ;; +esac + +case $PLATFORM in + x64|x86_64|x86-64|win64|Win64 ) + ARCHNAME=x86-64 + ARCHSUFFIX=64 + BITS=64 + + BASE_OPTS="-G\"$GENERATOR Win64\"" + add_cmake_opts "-G\"$GENERATOR Win64\"" + ;; + + x32|x86|i686|i386|win32|Win32 ) + ARCHNAME=x86 + ARCHSUFFIX=86 + BITS=32 + + BASE_OPTS="-G\"$GENERATOR\" -T$XP_TOOLSET" + add_cmake_opts "-G\"$GENERATOR\"" -T$XP_TOOLSET + ;; + + * ) + echo "Unknown platform $PLATFORM." + exit 1 + ;; +esac + +if ! [ -z $UNITY_BUILD ]; then + add_cmake_opts "-DOPENMW_UNITY_BUILD=True" +fi + +case $CONFIGURATION in + debug|Debug|DEBUG ) + CONFIGURATION=Debug + ;; + + release|Release|RELEASE ) + CONFIGURATION=Release + ;; + + relwithdebinfo|RelWithDebInfo|RELWITHDEBINFO ) + CONFIGURATION=RelWithDebInfo + ;; +esac + +echo +echo "==========================" +echo "Starting prebuild on win$BITS" +echo "==========================" +echo + +# cd OpenMW/AppVeyor-test +mkdir -p deps +cd deps + +DEPS="`pwd`" + +if [ -z $SKIP_DOWNLOAD ]; then + echo "Downloading dependency packages." + echo + + # Boost + if [ -z $APPVEYOR ]; then + download "Boost 1.58.0" \ + http://sourceforge.net/projects/boost/files/boost-binaries/1.58.0/boost_1_58_0-msvc-12.0-$BITS.exe \ + boost-1.58.0-win$BITS.exe + fi + + # Bullet + download "Bullet 2.83.5" \ + http://www.lysator.liu.se/~ace/OpenMW/deps/Bullet-2.83.5-win$BITS.7z \ + Bullet-2.83.5-win$BITS.7z + + # FFmpeg + download "FFmpeg 2.5.2" \ + http://ffmpeg.zeranoe.com/builds/win$BITS/shared/ffmpeg-2.5.2-win$BITS-shared.7z \ + ffmpeg$BITS-2.5.2.7z \ + http://ffmpeg.zeranoe.com/builds/win$BITS/dev/ffmpeg-2.5.2-win$BITS-dev.7z \ + ffmpeg$BITS-2.5.2-dev.7z + + # MyGUI + download "MyGUI 3.2.2" \ + http://www.lysator.liu.se/~ace/OpenMW/deps/MyGUI-3.2.2-win$BITS.7z \ + MyGUI-3.2.2-win$BITS.7z + + # OpenAL + download "OpenAL-Soft 1.16.0" \ + http://kcat.strangesoft.net/openal-binaries/openal-soft-1.16.0-bin.zip \ + OpenAL-Soft-1.16.0.zip + + # OSG + download "OpenSceneGraph 3.3.8" \ + http://www.lysator.liu.se/~ace/OpenMW/deps/OSG-3.3.8-win$BITS.7z \ + OSG-3.3.8-win$BITS.7z + + # Qt + if [ -z $APPVEYOR ]; then + download "Qt 4.8.6" \ + http://sourceforge.net/projects/qt64ng/files/qt/$ARCHNAME/4.8.6/msvc2013/qt-4.8.6-x$ARCHSUFFIX-msvc2013.7z \ + qt$BITS-4.8.6.7z + fi + + # SDL2 + download "SDL 2.0.3" \ + https://www.libsdl.org/release/SDL2-devel-2.0.3-VC.zip \ + SDL2-2.0.3.zip +fi + +cd .. #/.. + +# Set up dependencies +if [ -z $KEEP ]; then + echo + printf "Preparing build directory... " + + rm -rf Build_$BITS + mkdir -p Build_$BITS/deps + + echo Done. +fi +mkdir -p Build_$BITS/deps +cd Build_$BITS/deps + +DEPS_INSTALL=`pwd` +cd $DEPS + +echo +echo "Extracting dependencies..." + + +# Boost +printf "Boost 1.58.0... " +{ + if [ -z $APPVEYOR ]; then + cd $DEPS_INSTALL + + BOOST_SDK="`real_pwd`/Boost" + + if [ -d Boost ] && grep "BOOST_VERSION 105800" Boost/boost/version.hpp > /dev/null; then + printf "Exists. " + elif [ -z $SKIP_EXTRACT ]; then + rm -rf Boost + $DEPS/boost-1.58.0-win$BITS.exe //dir="$(echo $BOOST_SDK | sed s,/,\\\\,g)" //verysilent + fi + + add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \ + -DBOOST_LIBRARYDIR="$BOOST_SDK/lib$BITS-msvc-12.0" + + echo Done. + else + # Appveyor unstable has all the boost we need already + BOOST_SDK="c:/Libraries/boost" + add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \ + -DBOOST_LIBRARYDIR="$BOOST_SDK/lib$BITS-msvc-12.0" + + echo AppVeyor. + fi +} +cd $DEPS + +# Bullet +printf "Bullet 2.83.5... " +{ + cd $DEPS_INSTALL + + if [ -d Bullet ]; then + printf "Exists. (No version checking) " + elif [ -z $SKIP_EXTRACT ]; then + rm -rf Bullet + eval 7z x -y $DEPS/Bullet-2.83.5-win$BITS.7z $STRIP + mv Bullet-2.83.5-win$BITS Bullet + fi + + export BULLET_ROOT="`real_pwd`/Bullet" + + echo Done. +} +cd $DEPS + +# FFmpeg +printf "FFmpeg 2.5.2... " +{ + cd $DEPS_INSTALL + + if [ -d FFmpeg ] && grep "FFmpeg version: 2.5.2" FFmpeg/README.txt > /dev/null; then + printf "Exists. " + elif [ -z $SKIP_EXTRACT ]; then + rm -rf FFmpeg + + eval 7z x -y $DEPS/ffmpeg$BITS-2.5.2.7z $STRIP + eval 7z x -y $DEPS/ffmpeg$BITS-2.5.2-dev.7z $STRIP + + mv ffmpeg-2.5.2-win$BITS-shared FFmpeg + cp -r ffmpeg-2.5.2-win$BITS-dev/* FFmpeg/ + rm -rf ffmpeg-2.5.2-win$BITS-dev + fi + + export FFMPEG_HOME="`real_pwd`/FFmpeg" + add_runtime_dlls `pwd`/FFmpeg/bin/{avcodec-56,avformat-56,avutil-54,swresample-1,swscale-3}.dll + + if [ $BITS -eq 32 ]; then + add_cmake_opts "-DCMAKE_EXE_LINKER_FLAGS=\"/machine:X86 /safeseh:no\"" + fi + + echo Done. +} +cd $DEPS + +# MyGUI +printf "MyGUI 3.2.2... " +{ + cd $DEPS_INSTALL + + if [ -d MyGUI ] && \ + grep "MYGUI_VERSION_MAJOR 3" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null && \ + grep "MYGUI_VERSION_MINOR 2" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null && \ + grep "MYGUI_VERSION_PATCH 2" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null + then + printf "Exists. " + elif [ -z $SKIP_EXTRACT ]; then + rm -rf MyGUI + eval 7z x -y $DEPS/MyGUI-3.2.2-win$BITS.7z $STRIP + mv MyGUI-3.2.2-win$BITS MyGUI + fi + + MYGUI_SDK="`real_pwd`/MyGUI" + + add_cmake_opts -DMYGUISDK="$MYGUI_SDK" \ + -DMYGUI_INCLUDE_DIRS="$MYGUI_SDK/include/MYGUI" \ + -DMYGUI_PREQUEST_FILE="$MYGUI_SDK/include/MYGUI/MyGUI_Prerequest.h" + + if [ $CONFIGURATION == "Debug" ]; then + SUFFIX="_d" + else + SUFFIX="" + fi + add_runtime_dlls `pwd`/MyGUI/bin/$CONFIGURATION/MyGUIEngine$SUFFIX.dll + + echo Done. +} +cd $DEPS + +# OpenAL +printf "OpenAL-Soft 1.16.0... " +{ + if [ -d openal-soft-1.16.0-bin ]; then + printf "Exists. " + elif [ -z $SKIP_EXTRACT ]; then + rm -rf openal-soft-1.16.0-bin + eval 7z x -y OpenAL-Soft-1.16.0.zip $STRIP + fi + + OPENAL_SDK="`real_pwd`/openal-soft-1.16.0-bin" + + add_cmake_opts -DOPENAL_INCLUDE_DIR="$OPENAL_SDK/include/AL" \ + -DOPENAL_LIBRARY="$OPENAL_SDK/libs/Win$BITS/OpenAL32.lib" + + echo Done. +} +cd $DEPS + +# OSG +printf "OSG 3.3.8... " +{ + cd $DEPS_INSTALL + + if [ -d OSG ] && \ + grep "OPENSCENEGRAPH_MAJOR_VERSION 3" OSG/include/osg/Version > /dev/null && \ + grep "OPENSCENEGRAPH_MINOR_VERSION 3" OSG/include/osg/Version > /dev/null && \ + grep "OPENSCENEGRAPH_PATCH_VERSION 8" OSG/include/osg/Version > /dev/null + then + printf "Exists. " + elif [ -z $SKIP_EXTRACT ]; then + rm -rf OSG + eval 7z x -y $DEPS/OSG-3.3.8-win$BITS.7z $STRIP + mv OSG-3.3.8-win$BITS OSG + fi + + OSG_SDK="`real_pwd`/OSG" + + add_cmake_opts -DOSG_DIR="$OSG_SDK" + + if [ $CONFIGURATION == "Debug" ]; then + SUFFIX="d" + else + SUFFIX="" + fi + + add_runtime_dlls `pwd`/OSG/bin/{OpenThreads,zlib}$SUFFIX.dll \ + `pwd`/OSG/bin/osg{,Animation,DB,FX,GA,Particle,Qt,Text,Util,Viewer}$SUFFIX.dll + + add_osg_dlls `pwd`/OSG/bin/osgPlugins-3.3.8/osgdb_{bmp,dds,gif,jpeg,png,tga}$SUFFIX.dll + + echo Done. +} +cd $DEPS + +# Qt +if [ -z $APPVEYOR ]; then + printf "Qt 4.8.6... " +else + printf "Qt 5.4... " +fi +{ + if [ -z $APPVEYOR ]; then + cd $DEPS_INSTALL + QT_SDK="`real_pwd`/Qt" + + if [ -d Qt ] && head -n2 Qt/BUILDINFO.txt | grep "4.8.6" > /dev/null; then + printf "Exists. " + elif [ -z $SKIP_EXTRACT ]; then + rm -rf Qt + eval 7z x -y $DEPS/qt$BITS-4.8.6.7z $STRIP + mv qt-4.8.6-* Qt + cd Qt + eval ./qtbinpatcher.exe $STRIP + fi + + cd $QT_SDK + + add_cmake_opts -DDESIRED_QT_VERSION=4 \ + -DQT_QMAKE_EXECUTABLE="$QT_SDK/bin/qmake.exe" + + if [ $CONFIGURATION == "Debug" ]; then + SUFFIX="d4" + else + SUFFIX="4" + fi + + add_runtime_dlls `pwd`/bin/Qt{Core,Gui,Network,OpenGL}$SUFFIX.dll + + echo Done. + else + if [ $BITS -eq 32 ]; then + QT_SDK="C:/Qt/5.4/msvc2013_opengl" + else + QT_SDK="C:/Qt/5.4/msvc2013_64_opengl" + fi + + add_cmake_opts -DDESIRED_QT_VERSION=5 \ + -DQT_QMAKE_EXECUTABLE="$QT_SDK/bin/qmake.exe" \ + -DCMAKE_PREFIX_PATH="$QT_SDK" + + echo AppVeyor. + fi +} +cd $DEPS + +# SDL2 +printf "SDL 2.0.3... " +{ + if [ -d SDL2-2.0.3 ]; then + printf "Exists. " + elif [ -z $SKIP_EXTRACT ]; then + rm -rf SDL2-2.0.3 + eval 7z x -y SDL2-2.0.3.zip $STRIP + fi + + SDL_SDK="`real_pwd`/SDL2-2.0.3" + add_cmake_opts -DSDL2_INCLUDE_DIR="$SDL_SDK/include" \ + -DSDL2MAIN_LIBRARY="$SDL_SDK/lib/x$ARCHSUFFIX/SDL2main.lib" \ + -DSDL2_LIBRARY_PATH="$SDL_SDK/lib/x$ARCHSUFFIX/SDL2.lib" + + add_runtime_dlls `pwd`/SDL2-2.0.3/lib/x$ARCHSUFFIX/SDL2.dll + + echo Done. +} + + +cd $DEPS_INSTALL/.. + +echo +echo "Setting up OpenMW build..." + +add_cmake_opts -DBUILD_BSATOOL=no \ + -DBUILD_ESMTOOL=no \ + -DBUILD_MYGUI_PLUGIN=no \ + -DOPENMW_MP_BUILD=on + +if [ -z $CI ]; then + echo " (Outside of CI, doing full build.)" +else + case $STEP in + components ) + echo " Subproject: Components." + add_cmake_opts -DBUILD_ESSIMPORTER=no \ + -DBUILD_LAUNCHER=no \ + -DBUILD_MWINIIMPORTER=no \ + -DBUILD_OPENCS=no \ + -DBUILD_OPENMW=no \ + -DBUILD_WIZARD=no + ;; + openmw ) + echo " Subproject: OpenMW." + add_cmake_opts -DBUILD_ESSIMPORTER=no \ + -DBUILD_LAUNCHER=no \ + -DBUILD_MWINIIMPORTER=no \ + -DBUILD_OPENCS=no \ + -DBUILD_WIZARD=no + ;; + opencs ) + echo " Subproject: OpenCS." + add_cmake_opts -DBUILD_ESSIMPORTER=no \ + -DBUILD_LAUNCHER=no \ + -DBUILD_MWINIIMPORTER=no \ + -DBUILD_OPENMW=no \ + -DBUILD_WIZARD=no + ;; + misc ) + echo " Subproject: Misc." + add_cmake_opts -DBUILD_OPENCS=no \ + -DBUILD_OPENMW=no + ;; + * ) + echo " Building everything." + ;; + esac +fi + +if [ -z $VERBOSE ]; then + printf " Configuring... " +else + echo " cmake .. $CMAKE_OPTS" +fi + +run_cmd cmake .. $CMAKE_OPTS +RET=$? + +if [ -z $VERBOSE ]; then + if [ $RET -eq 0 ]; then echo Done. + else echo Failed.; fi +fi + +echo + +# NOTE: Disable this when/if we want to run test cases +if [ -z $CI ]; then + echo "Copying Runtime DLLs..." + mkdir -p $CONFIGURATION + for DLL in $RUNTIME_DLLS; do + echo " `basename $DLL`." + cp "$DLL" $CONFIGURATION/ + done + echo "OSG Plugin DLLs..." + mkdir -p $CONFIGURATION/osgPlugins-3.3.8 + for DLL in $OSG_PLUGINS; do + echo " `basename $DLL`." + cp "$DLL" $CONFIGURATION/osgPlugins-3.3.8 + done + echo + + echo "Copying Runtime Resources/Config Files" + + echo " gamecontrollerdb.txt" + cp $CONFIGURATION/../gamecontrollerdb.txt $CONFIGURATION/gamecontrollerdb.txt + echo " openmw.cfg" + cp $CONFIGURATION/../openmw.cfg.install $CONFIGURATION/openmw.cfg + echo " openmw-cs.cfg" + cp $CONFIGURATION/../openmw-cs.cfg $CONFIGURATION/openmw-cs.cfg + echo " settings-default.cfg" + cp $CONFIGURATION/../settings-default.cfg $CONFIGURATION/settings-default.cfg + echo " resources/" + cp -r $CONFIGURATION/../resources $CONFIGURATION/resources +fi + +exit $RET diff --git a/CI/before_script.osx.sh b/CI/before_script.osx.sh index 772284f914..bf38186f62 100755 --- a/CI/before_script.osx.sh +++ b/CI/before_script.osx.sh @@ -1,5 +1,25 @@ #!/bin/sh +export CXX=clang++ +export CC=clang + +DEPENDENCIES_ROOT="/private/tmp/openmw-deps/openmw-deps" +QT_PATH="/usr/local/opt/qt5" + mkdir build cd build -cmake -DCMAKE_FRAMEWORK_PATH="/usr/local/lib/macosx/Release" -DCMAKE_EXE_LINKER_FLAGS="-F/usr/local/lib/macosx/Release" -DCMAKE_CXX_FLAGS="-stdlib=libstdc++" -DCMAKE_BUILD_TYPE=Debug -DBUILD_MYGUI_PLUGIN=OFF -G"Unix Makefiles" .. + +cmake \ +-D PKG_CONFIG_USE_CMAKE_PREFIX_PATH=ON \ +-D CMAKE_EXE_LINKER_FLAGS="-lz" \ +-D CMAKE_PREFIX_PATH="$DEPENDENCIES_ROOT;$QT_PATH" \ +-D CMAKE_OSX_DEPLOYMENT_TARGET="10.8" \ +-D CMAKE_OSX_SYSROOT="macosx10.11" \ +-D CMAKE_BUILD_TYPE=Debug \ +-D OPENMW_OSX_DEPLOYMENT=TRUE \ +-D DESIRED_QT_VERSION=5 \ +-D OSG_PLUGIN_LIB_SEARCH_PATH="$DEPENDENCIES_ROOT/lib/osgPlugins-3.4.0" \ +-D BUILD_ESMTOOL=FALSE \ +-D BUILD_MYGUI_PLUGIN=FALSE \ +-G"Unix Makefiles" \ +.. diff --git a/CI/build.msvc.sh b/CI/build.msvc.sh new file mode 100644 index 0000000000..731c51eda0 --- /dev/null +++ b/CI/build.msvc.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +if [ -z $PLATFORM ]; then + PLATFORM=`uname -m` +fi + +if [ -z $CONFIGURATION ]; then + CONFIGURATION="Debug" +fi + +case $PLATFORM in + x32|x86|i686|i386|win32|Win32 ) + BITS=32 + PLATFORM=Win32 + ;; + + x64|x86_64|x86-64|win64|Win64 ) + BITS=64 + PLATFORM=x64 + ;; + + * ) + echo "Unknown platform $PLATFORM." + exit 1 ;; +esac + +if [ -z $APPVEYOR ]; then + echo "Running $BITS-bit $CONFIGURATION build outside of Appveyor." + + DIR=$(echo "$0" | sed "s,\\\\,/,g" | sed "s,\(.\):,/\\1,") + cd $(dirname "$DIR")/.. +else + echo "Running $BITS-bit $CONFIGURATION build in Appveyor." + + cd $APPVEYOR_BUILD_FOLDER +fi + +cd build_$BITS + +which msbuild > /dev/null +if [ $? -ne 0 ]; then + msbuild() { + /c/Program\ Files\ \(x86\)/MSBuild/12.0/Bin/MSBuild.exe "$@" + } +fi + +if [ -z $APPVEYOR ]; then + msbuild OpenMW.sln //t:Build //m:8 +else + msbuild OpenMW.sln //t:Build //m:8 //logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" +fi + +RET=$? +if [ $RET -eq 0 ] && [ ! -z $PACKAGE ]; then + msbuild PACKAGE.vcxproj //t:Build //m:8 + RET=$? +fi + +exit $RET diff --git a/CMakeLists.txt b/CMakeLists.txt index afe3ce4b70..14a1c15c80 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,11 +16,16 @@ endif (APPLE) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/) +if (ANDROID) + set(CMAKE_FIND_ROOT_PATH ${OPENMW_DEPENDENCIES_DIR} "${CMAKE_FIND_ROOT_PATH}") + set (OSG_PLUGINS_DIR CACHE STRING "") +endif() + # Version message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) -set(OPENMW_VERSION_MINOR 37) +set(OPENMW_VERSION_MINOR 39) set(OPENMW_VERSION_RELEASE 0) set(OPENMW_VERSION_COMMITHASH "") @@ -111,68 +116,12 @@ if (WIN32) option(USE_DEBUG_CONSOLE "whether a debug console should be enabled for debug builds, if false debug output is redirected to Visual Studio output" ON) endif() -# We probably support older versions than this. -cmake_minimum_required(VERSION 2.6) - -# Sound setup -unset(FFMPEG_LIBRARIES CACHE) - -find_package(FFmpeg REQUIRED) - -set (FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${SWSCALE_LIBRARY} ${SWRESAMPLE_LIBRARIES}) - -if ( NOT AVCODEC_FOUND OR NOT AVFORMAT_FOUND OR NOT AVUTIL_FOUND OR NOT SWSCALE_FOUND OR NOT SWRESAMPLE_FOUND) - message(FATAL_ERROR "FFmpeg component required, but not found!") -endif() -# Required for building the FFmpeg headers -add_definitions(-D__STDC_CONSTANT_MACROS) - -# TinyXML -option(USE_SYSTEM_TINYXML "Use system TinyXML library instead of internal." OFF) -if(USE_SYSTEM_TINYXML) - find_library(TINYXML_LIBRARIES tinyxml) - find_path(TINYXML_INCLUDE_DIR tinyxml.h) - message(STATUS "Found TinyXML: ${TINYXML_LIBRARIES} ${TINYXML_INCLUDE_DIR}") - add_definitions (-DTIXML_USE_STL) - if(TINYXML_LIBRARIES AND TINYXML_INCLUDE_DIR) - include_directories(${TINYXML_INCLUDE_DIR}) - message(STATUS "Using system TinyXML library.") - else() - message(FATAL_ERROR "Detection of system TinyXML incomplete.") - endif() -endif() - -# Platform specific -if (WIN32) - if(NOT MINGW) - set(Boost_USE_STATIC_LIBS ON) - add_definitions(-DBOOST_ALL_NO_LIB) - endif(NOT MINGW) - - # Suppress WinMain(), provided by SDL - add_definitions(-DSDL_MAIN_HANDLED) - - # Get rid of useless crud from windows.h - add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN) -endif() - -if (ANDROID) - set(CMAKE_FIND_ROOT_PATH ${OPENMW_DEPENDENCIES_DIR} "${CMAKE_FIND_ROOT_PATH}") - set(OPENGL_ES TRUE CACHE BOOL "enable opengl es support for android" FORCE) -endif (ANDROID) - -option(OPENGL_ES "enable opengl es support" FALSE ) - -if (OPENGL_ES) - add_definitions(-DOPENGL_ES) -endif(OPENGL_ES) - if (NOT BUILD_LAUNCHER AND NOT BUILD_OPENCS AND NOT BUILD_WIZARD) set(USE_QT FALSE) else() set(USE_QT TRUE) endif() - + # Dependencies if (USE_QT) set(DESIRED_QT_VERSION 4 CACHE STRING "The QT version OpenMW should use (4 or 5)") @@ -191,6 +140,52 @@ if (USE_QT) endif() endif() +if (USE_QT AND DESIRED_QT_VERSION MATCHES 5) + # 2.8.11+ is required to make Qt5 happy and allow linking QtMain on Windows. + cmake_minimum_required(VERSION 2.8.11) +else() + # We probably support older versions than this. + cmake_minimum_required(VERSION 2.6) +endif() + +# Sound setup +find_package(FFmpeg REQUIRED COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE SWRESAMPLE) +# Required for building the FFmpeg headers +add_definitions(-D__STDC_CONSTANT_MACROS) + +# TinyXML +option(USE_SYSTEM_TINYXML "Use system TinyXML library instead of internal." OFF) +if (USE_SYSTEM_TINYXML) + find_package(TinyXML REQUIRED) + add_definitions (-DTIXML_USE_STL) + include_directories(SYSTEM ${TinyXML_INCLUDE_DIRS}) +endif() + +# Platform specific +if (WIN32) + if(NOT MINGW) + set(Boost_USE_STATIC_LIBS ON) + add_definitions(-DBOOST_ALL_NO_LIB) + endif(NOT MINGW) + + # Suppress WinMain(), provided by SDL + add_definitions(-DSDL_MAIN_HANDLED) + + # Get rid of useless crud from windows.h + add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN) +endif() + +if (NOT WIN32 AND BUILD_WIZARD) # windows users can just run the morrowind installer + find_package(LIBUNSHIELD REQUIRED) # required only for non win32 when building openmw-wizard + set(OPENMW_USE_UNSHIELD TRUE) +endif() + +option(OPENGL_ES "enable opengl es support" FALSE ) + +if (OPENGL_ES) + add_definitions(-DOPENGL_ES) +endif(OPENGL_ES) + # Fix for not visible pthreads functions for linker with glibc 2.15 if (UNIX AND NOT APPLE) find_package (Threads) @@ -204,12 +199,6 @@ if(NOT HAVE_STDINT_H) message(FATAL_ERROR "stdint.h was not found" ) endif() -include (CheckIncludeFileCXX) -check_include_file_cxx(unordered_map HAVE_UNORDERED_MAP) -if (HAVE_UNORDERED_MAP) - add_definitions(-DHAVE_UNORDERED_MAP) -endif () - set(BOOST_COMPONENTS system filesystem program_options thread) if(WIN32) @@ -220,11 +209,7 @@ IF(BOOST_STATIC) set(Boost_USE_STATIC_LIBS ON) endif() -if (USE_QT) - set (OSG_QT osgQt) -endif() - -find_package(OpenSceneGraph 3.2.0 REQUIRED osgDB osgViewer osgText osgGA osgAnimation osgParticle ${OSG_QT} osgUtil osgFX) +find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgAnimation osgParticle osgUtil osgFX) include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}) @@ -234,6 +219,7 @@ if(OSG_STATIC) # For now, users wishing to do a static build will need to pass the path to where the plugins reside # More clever logic would need to deduce the path, probably installed under /lib/osgPlugins- + include(FindPkgMacros) find_library(${PLUGIN_NAME}_LIBRARY_REL NAMES ${PLUGIN_NAME} HINTS ${OSG_PLUGIN_LIB_SEARCH_PATH}) find_library(${PLUGIN_NAME}_LIBRARY_DBG NAMES ${PLUGIN_NAME_DBG} HINTS ${OSG_PLUGIN_LIB_SEARCH_PATH}) make_library_set(${PLUGIN_NAME}_LIBRARY) @@ -245,12 +231,6 @@ if(OSG_STATIC) set(OPENSCENEGRAPH_LIBRARIES ${OPENSCENEGRAPH_LIBRARIES} ${${PLUGIN_NAME}_LIBRARY}) endmacro() - macro(use_static_osg_plugin_dep DEPENDENCY) - find_package(${DEPENDENCY} REQUIRED) - - set(OPENSCENEGRAPH_LIBRARIES ${OPENSCENEGRAPH_LIBRARIES} ${${DEPENDENCY}_LIBRARIES}) - endmacro() - add_definitions(-DOSG_LIBRARY_STATIC) set(PLUGIN_LIST @@ -271,6 +251,11 @@ if(OSG_STATIC) JPEG # needed by osgdb_jpeg ) + macro(use_static_osg_plugin_dep DEPENDENCY) + find_package(${DEPENDENCY} REQUIRED) + + set(OPENSCENEGRAPH_LIBRARIES ${OPENSCENEGRAPH_LIBRARIES} ${${DEPENDENCY}_LIBRARIES}) + endmacro() foreach(DEPENDENCY ${PLUGIN_DEPS_LIST}) use_static_osg_plugin_dep(${DEPENDENCY}) endforeach() @@ -294,7 +279,7 @@ endif() find_package(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS}) find_package(SDL2 REQUIRED) find_package(OpenAL REQUIRED) -find_package(Bullet REQUIRED) +find_package(Bullet 283 REQUIRED COMPONENTS BulletCollision LinearMath) include_directories("." SYSTEM @@ -302,7 +287,7 @@ include_directories("." ${Boost_INCLUDE_DIR} ${MYGUI_INCLUDE_DIRS} ${OPENAL_INCLUDE_DIR} - ${BULLET_INCLUDE_DIRS} + ${Bullet_INCLUDE_DIRS} ) link_directories(${SDL2_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS} ${MYGUI_LIB_DIR}) @@ -369,21 +354,17 @@ endif() # CXX Compiler settings if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-unused-parameter -std=c++98 -pedantic -Wno-long-long") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wundef -Wno-unused-parameter -std=c++98 -pedantic -Wno-long-long") if (CMAKE_CXX_COMPILER_ID STREQUAL Clang AND NOT APPLE) - execute_process(COMMAND ${CMAKE_C_COMPILER} --version OUTPUT_VARIABLE CLANG_VERSION) - string(REGEX REPLACE ".*version ([0-9\\.]*).*" "\\1" CLANG_VERSION ${CLANG_VERSION}) - if ("${CLANG_VERSION}" VERSION_GREATER 3.6 OR "${CLANG_VERSION}" VERSION_EQUAL 3.6) + if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 3.6 OR CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 3.6) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-potentially-evaluated-expression") - endif ("${CLANG_VERSION}" VERSION_GREATER 3.6 OR "${CLANG_VERSION}" VERSION_EQUAL 3.6) - endif(CMAKE_CXX_COMPILER_ID STREQUAL Clang AND NOT APPLE) + endif () + endif() - execute_process(COMMAND ${CMAKE_C_COMPILER} -dumpversion - OUTPUT_VARIABLE GCC_VERSION) - if (CMAKE_CXX_COMPILER_ID STREQUAL GNU AND "${GCC_VERSION}" VERSION_GREATER 4.6 OR "${GCC_VERSION}" VERSION_EQUAL 4.6) + if (CMAKE_CXX_COMPILER_ID STREQUAL GNU AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.6 OR CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 4.6) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-but-set-parameter") - endif(CMAKE_CXX_COMPILER_ID STREQUAL GNU AND "${GCC_VERSION}" VERSION_GREATER 4.6 OR "${GCC_VERSION}" VERSION_EQUAL 4.6) + endif() elseif (MSVC) # Enable link-time code generation globally for all linking if (OPENMW_LTO_BUILD) @@ -558,6 +539,9 @@ endif(WIN32) # Extern add_subdirectory (extern/osg-ffmpeg-videoplayer) add_subdirectory (extern/oics) +if (USE_QT) + add_subdirectory (extern/osgQt) +endif() # Components add_subdirectory (components) @@ -685,8 +669,6 @@ if (WIN32) endforeach(d) set_target_properties(components PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") - # oics uses tinyxml, which has an initialized but unused variable - set_target_properties(oics PROPERTIES COMPILE_FLAGS "${WARNINGS} /wd4189 ${MT_BUILD}") set_target_properties(osg-ffmpeg-videoplayer PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") if (BUILD_BSATOOL) diff --git a/README.md b/README.md index f353cd76ef..b1db74549d 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ OpenMW ====== -[![Build Status](https://img.shields.io/travis/OpenMW/openmw.svg)](https://travis-ci.org/OpenMW/openmw) [![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740) +[![Build Status](https://api.travis-ci.org/OpenMW/openmw.svg)](https://travis-ci.org/OpenMW/openmw) [![Build status](https://ci.appveyor.com/api/projects/status/e6bqw8oouy8ufd46?svg=true)](https://ci.appveyor.com/project/scrawl/openmw) [![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740) OpenMW is a recreation of the engine for the popular role-playing game Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work. OpenMW also comes with OpenMW-CS, a replacement for Morrowind's TES Construction Set. -* Version: 0.37.0 +* Version: 0.39.0 * License: GPL (see docs/license/GPL3.txt for more information) * Website: http://www.openmw.org * IRC: #openmw on irc.freenode.net diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index be90afec38..6d2b59ad98 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include @@ -354,12 +355,12 @@ int load(Arguments& info) esm.getRecHeader(flags); EsmTool::RecordBase *record = EsmTool::RecordBase::create(n); - if (record == 0) + if (record == 0) { - if (std::find(skipped.begin(), skipped.end(), n.val) == skipped.end()) + if (std::find(skipped.begin(), skipped.end(), n.intval) == skipped.end()) { std::cout << "Skipping " << n.toString() << " records." << std::endl; - skipped.push_back(n.val); + skipped.push_back(n.intval); } esm.skipRecord(); @@ -391,20 +392,20 @@ int load(Arguments& info) record->print(); } - if (record->getType().val == ESM::REC_CELL && loadCells && interested) + if (record->getType().intval == ESM::REC_CELL && loadCells && interested) { loadCell(record->cast()->get(), esm, info); } - if (save) + if (save) { info.data.mRecords.push_back(record); - } - else + } + else { delete record; } - ++info.data.mRecordStats[n.val]; + ++info.data.mRecordStats[n.intval]; } } catch(std::exception &e) { @@ -442,23 +443,18 @@ int clone(Arguments& info) size_t recordCount = info.data.mRecords.size(); int digitCount = 1; // For a nicer output - if (recordCount > 9) ++digitCount; - if (recordCount > 99) ++digitCount; - if (recordCount > 999) ++digitCount; - if (recordCount > 9999) ++digitCount; - if (recordCount > 99999) ++digitCount; - if (recordCount > 999999) ++digitCount; + if (recordCount > 0) + digitCount = (int)std::log10(recordCount) + 1; std::cout << "Loaded " << recordCount << " records:" << std::endl << std::endl; - ESM::NAME name; - int i = 0; typedef std::map Stats; Stats &stats = info.data.mRecordStats; for (Stats::iterator it = stats.begin(); it != stats.end(); ++it) { - name.val = it->first; + ESM::NAME name; + name.intval = it->first; int amount = it->second; std::cout << std::setw(digitCount) << amount << " " << name.toString() << " "; @@ -491,12 +487,12 @@ int clone(Arguments& info) for (Records::iterator it = records.begin(); it != records.end() && i > 0; ++it) { EsmTool::RecordBase *record = *it; - name.val = record->getType().val; + const ESM::NAME& typeName = record->getType(); - esm.startRecord(name.toString(), record->getFlags()); + esm.startRecord(typeName.toString(), record->getFlags()); record->save(esm); - if (name.val == ESM::REC_CELL) { + if (typeName.intval == ESM::REC_CELL) { ESM::Cell *ptr = &record->cast()->get(); if (!info.data.mCellRefs[ptr].empty()) { typedef std::deque > RefList; @@ -508,7 +504,7 @@ int clone(Arguments& info) } } - esm.endRecord(name.toString()); + esm.endRecord(typeName.toString()); saved++; int perc = (int)((saved / (float)recordCount)*100); diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index d7cbea73b0..2382612a4d 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -179,7 +179,7 @@ RecordBase::create(ESM::NAME type) { RecordBase *record = 0; - switch (type.val) { + switch (type.intval) { case ESM::REC_ACTI: { record = new EsmTool::Record; diff --git a/apps/essimporter/converter.hpp b/apps/essimporter/converter.hpp index f364e166c8..11f9664468 100644 --- a/apps/essimporter/converter.hpp +++ b/apps/essimporter/converter.hpp @@ -121,7 +121,7 @@ public: { mContext->mPlayer.mObject.mCreatureStats.mLevel = npc.mNpdt52.mLevel; mContext->mPlayerBase = npc; - std::map empty; + ESM::SpellState::SpellParams empty; // FIXME: player start spells and birthsign spells aren't listed here, // need to fix openmw to account for this for (std::vector::const_iterator it = npc.mSpells.mList.begin(); it != npc.mSpells.mList.end(); ++it) diff --git a/apps/essimporter/convertinventory.cpp b/apps/essimporter/convertinventory.cpp index f476fe1ee2..0799c8d113 100644 --- a/apps/essimporter/convertinventory.cpp +++ b/apps/essimporter/convertinventory.cpp @@ -1,6 +1,7 @@ #include "convertinventory.hpp" #include +#include namespace ESSImport { diff --git a/apps/essimporter/convertplayer.cpp b/apps/essimporter/convertplayer.cpp index 5718201f74..55aaad1c56 100644 --- a/apps/essimporter/convertplayer.cpp +++ b/apps/essimporter/convertplayer.cpp @@ -17,6 +17,8 @@ namespace ESSImport faction.mReputation = it->mReputation; out.mObject.mNpcStats.mFactions[Misc::StringUtils::lowerCase(it->mFactionName.toString())] = faction; } + for (int i=0; i<3; ++i) + out.mObject.mNpcStats.mSpecIncreases[i] = pcdt.mPNAM.mSpecIncreases[i]; for (int i=0; i<8; ++i) out.mObject.mNpcStats.mSkillIncrease[i] = pcdt.mPNAM.mSkillIncreases[i]; for (int i=0; i<27; ++i) @@ -28,7 +30,7 @@ namespace ESSImport if (pcdt.mPNAM.mDrawState & PCDT::DrawState_Spell) out.mObject.mCreatureStats.mDrawState = 2; - firstPersonCam = (pcdt.mPNAM.mCameraState == PCDT::CameraState_FirstPerson); + firstPersonCam = !(pcdt.mPNAM.mCameraFlags & PCDT::CameraFlag_ThirdPerson); for (std::vector::const_iterator it = pcdt.mKnownDialogueTopics.begin(); it != pcdt.mKnownDialogueTopics.end(); ++it) diff --git a/apps/essimporter/importacdt.cpp b/apps/essimporter/importacdt.cpp index 239c46698d..fb0dccaf5e 100644 --- a/apps/essimporter/importacdt.cpp +++ b/apps/essimporter/importacdt.cpp @@ -75,7 +75,7 @@ namespace ESSImport // unsure at which point between TGTN and CRED if (esm.isNextSub("AADT")) { - // occured when a creature was in the middle of its attack, 44 bytes + // occurred when a creature was in the middle of its attack, 44 bytes esm.skipHSub(); } diff --git a/apps/essimporter/importer.cpp b/apps/essimporter/importer.cpp index 4fbf062170..f17db309f4 100644 --- a/apps/essimporter/importer.cpp +++ b/apps/essimporter/importer.cpp @@ -54,7 +54,7 @@ namespace *(image->data(x,y)+2) = *it++; *(image->data(x,y)+1) = *it++; *image->data(x,y) = *it++; - it++; // skip alpha + ++it; // skip alpha } } @@ -322,14 +322,14 @@ namespace ESSImport ESM::NAME n = esm.getRecName(); esm.getRecHeader(); - std::map >::iterator it = converters.find(n.val); + std::map >::iterator it = converters.find(n.intval); if (it != converters.end()) { it->second->read(esm); } else { - if (unknownRecords.insert(n.val).second) + if (unknownRecords.insert(n.intval).second) { std::ios::fmtflags f(std::cerr.flags()); std::cerr << "unknown record " << n.toString() << " (0x" << std::hex << esm.getFileOffset() << ")" << std::endl; diff --git a/apps/essimporter/importinventory.cpp b/apps/essimporter/importinventory.cpp index 3ec640d3d5..2c87a9a19d 100644 --- a/apps/essimporter/importinventory.cpp +++ b/apps/essimporter/importinventory.cpp @@ -21,13 +21,18 @@ namespace ESSImport item.mCount = contItem.mCount; item.mRelativeEquipmentSlot = -1; - // seems that a stack of items can have a set of subrecords for each item? rings0000.ess - // doesn't make any sense to me, if the values were different then the items shouldn't stack in the first place? - // I guess we should double check the stacking logic in OpenMW - for (int i=0;i #include #include #include @@ -897,8 +898,13 @@ std::time_t MwIniImporter::lastWriteTime(const boost::filesystem::path& filename boost::filesystem::path resolved = filename; #endif writeTime = boost::filesystem::last_write_time(resolved); - std::cout << "content file: " << resolved << " timestamp = (" << writeTime << - ") " << asctime(localtime(&writeTime)) << std::endl; + + // print timestamp + const int size=1024; + char timeStrBuffer[size]; + if (std::strftime(timeStrBuffer, size, "%x %X", localtime(&writeTime)) > 0) + std::cout << "content file: " << resolved << " timestamp = (" << writeTime << + ") " << timeStrBuffer << std::endl; } else { diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 0e9a49432b..f8cf1e2d80 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -26,7 +26,7 @@ opencs_units_noqt (model/world universalid record commands columnbase columnimp scriptcontext cell refidcollection refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection resources resourcesmanager scope pathgrid landtexture land nestedtablewrapper nestedcollection nestedcoladapterimp nestedinfocollection - idcompletionmanager metadata + idcompletionmanager metadata defaultgmsts infoselectwrapper commandmacro ) opencs_hdrs_noqt (model/world @@ -35,14 +35,14 @@ opencs_hdrs_noqt (model/world opencs_units (model/tools - tools reportmodel mergeoperation + tools reportmodel mergeoperation ) opencs_units_noqt (model/tools mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck magiceffectcheck - mergestages + mergestages gmstcheck topicinfocheck journalcheck ) opencs_hdrs_noqt (model/tools @@ -67,7 +67,7 @@ opencs_hdrs_noqt (view/doc opencs_units (view/world table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator - cellcreator referenceablecreator startscriptcreator referencecreator scenesubview + cellcreator pathgridcreator referenceablecreator startscriptcreator referencecreator scenesubview infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable nestedtable dialoguespinbox recordbuttonbar tableeditidaction scripterrortable extendedcommandconfigurator ) @@ -85,16 +85,17 @@ opencs_units (view/widget opencs_units (view/render scenewidget worldspacewidget pagedworldspacewidget unpagedworldspacewidget - previewwidget editmode instancemode + previewwidget editmode instancemode instanceselectionmode instancemovemode + orbitcameramode pathgridmode selectionmode pathgridselectionmode ) opencs_units_noqt (view/render - lighting lightingday lightingnight - lightingbright object cell terrainstorage tagbase cellarrow + lighting lightingday lightingnight lightingbright object cell terrainstorage tagbase + cellarrow cellmarker cellborder cameracontroller pathgrid ) opencs_hdrs_noqt (view/render - elements + mask ) @@ -192,11 +193,12 @@ endif(APPLE) target_link_libraries(openmw-cs ${OSG_LIBRARIES} ${OPENTHREADS_LIBRARIES} + ${OSGTEXT_LIBRARIES} ${OSGUTIL_LIBRARIES} ${OSGVIEWER_LIBRARIES} ${OSGGA_LIBRARIES} ${OSGFX_LIBRARIES} - ${OSGQT_LIBRARIES} + ${EXTERN_OSGQT_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} @@ -215,9 +217,6 @@ if (DESIRED_QT_VERSION MATCHES 4) endif() else() qt5_use_modules(openmw-cs Widgets Core Network OpenGL) - if (WIN32) - target_link_libraries(Qt5::WinMain) - endif() endif() if (WIN32) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 92f1082b75..54e323956d 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -8,6 +8,8 @@ #include #include +#include + #include #include "model/doc/document.hpp" @@ -17,6 +19,8 @@ #include #endif +using namespace Fallback; + CS::Editor::Editor () : mSettingsState (mCfgMgr), mDocumentManager (mCfgMgr), mViewManager (mDocumentManager), mPid(""), @@ -100,6 +104,8 @@ std::pair > CS::Editor::readConfi ("resources", boost::program_options::value()->default_value("resources")) ("fallback-archive", boost::program_options::value >()-> default_value(std::vector(), "fallback-archive")->multitoken()) + ("fallback", boost::program_options::value()->default_value(FallbackMap(), "") + ->multitoken()->composing(), "fallback values") ("script-blacklist", boost::program_options::value >()->default_value(std::vector(), "") ->multitoken(), "exclude specified script from the verifier (if the use of the blacklist is enabled)") ("script-blacklist-use", boost::program_options::value()->implicit_value(true) @@ -114,6 +120,8 @@ std::pair > CS::Editor::readConfi mDocumentManager.setResourceDir (mResources = variables["resources"].as()); + mDocumentManager.setFallbackMap (variables["fallback"].as().mMap); + if (variables["script-blacklist-use"].as()) mDocumentManager.setBlacklistedScripts ( variables["script-blacklist"].as >()); diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 0e2d4e7d1a..87fbccb3f3 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -6,1925 +6,36 @@ #include +#include "../world/defaultgmsts.hpp" + #ifndef Q_MOC_RUN #include #endif void CSMDoc::Document::addGmsts() { - static const char *gmstFloats[] = - { - "fAIFleeFleeMult", - "fAIFleeHealthMult", - "fAIMagicSpellMult", - "fAIMeleeArmorMult", - "fAIMeleeSummWeaponMult", - "fAIMeleeWeaponMult", - "fAIRangeMagicSpellMult", - "fAIRangeMeleeWeaponMult", - "fAlarmRadius", - "fAthleticsRunBonus", - "fAudioDefaultMaxDistance", - "fAudioDefaultMinDistance", - "fAudioMaxDistanceMult", - "fAudioMinDistanceMult", - "fAudioVoiceDefaultMaxDistance", - "fAudioVoiceDefaultMinDistance", - "fAutoPCSpellChance", - "fAutoSpellChance", - "fBargainOfferBase", - "fBargainOfferMulti", - "fBarterGoldResetDelay", - "fBaseRunMultiplier", - "fBlockStillBonus", - "fBribe1000Mod", - "fBribe100Mod", - "fBribe10Mod", - "fCombatAngleXY", - "fCombatAngleZ", - "fCombatArmorMinMult", - "fCombatBlockLeftAngle", - "fCombatBlockRightAngle", - "fCombatCriticalStrikeMult", - "fCombatDelayCreature", - "fCombatDelayNPC", - "fCombatDistance", - "fCombatDistanceWerewolfMod", - "fCombatForceSideAngle", - "fCombatInvisoMult", - "fCombatKODamageMult", - "fCombatTorsoSideAngle", - "fCombatTorsoStartPercent", - "fCombatTorsoStopPercent", - "fConstantEffectMult", - "fCorpseClearDelay", - "fCorpseRespawnDelay", - "fCrimeGoldDiscountMult", - "fCrimeGoldTurnInMult", - "fCrimeStealing", - "fDamageStrengthBase", - "fDamageStrengthMult", - "fDifficultyMult", - "fDiseaseXferChance", - "fDispAttacking", - "fDispBargainFailMod", - "fDispBargainSuccessMod", - "fDispCrimeMod", - "fDispDiseaseMod", - "fDispFactionMod", - "fDispFactionRankBase", - "fDispFactionRankMult", - "fDispositionMod", - "fDispPersonalityBase", - "fDispPersonalityMult", - "fDispPickPocketMod", - "fDispRaceMod", - "fDispStealing", - "fDispWeaponDrawn", - "fEffectCostMult", - "fElementalShieldMult", - "fEnchantmentChanceMult", - "fEnchantmentConstantChanceMult", - "fEnchantmentConstantDurationMult", - "fEnchantmentMult", - "fEnchantmentValueMult", - "fEncumberedMoveEffect", - "fEncumbranceStrMult", - "fEndFatigueMult", - "fFallAcroBase", - "fFallAcroMult", - "fFallDamageDistanceMin", - "fFallDistanceBase", - "fFallDistanceMult", - "fFatigueAttackBase", - "fFatigueAttackMult", - "fFatigueBase", - "fFatigueBlockBase", - "fFatigueBlockMult", - "fFatigueJumpBase", - "fFatigueJumpMult", - "fFatigueMult", - "fFatigueReturnBase", - "fFatigueReturnMult", - "fFatigueRunBase", - "fFatigueRunMult", - "fFatigueSneakBase", - "fFatigueSneakMult", - "fFatigueSpellBase", - "fFatigueSpellCostMult", - "fFatigueSpellMult", - "fFatigueSwimRunBase", - "fFatigueSwimRunMult", - "fFatigueSwimWalkBase", - "fFatigueSwimWalkMult", - "fFightDispMult", - "fFightDistanceMultiplier", - "fFightStealing", - "fFleeDistance", - "fGreetDistanceReset", - "fHandtoHandHealthPer", - "fHandToHandReach", - "fHoldBreathEndMult", - "fHoldBreathTime", - "fIdleChanceMultiplier", - "fIngredientMult", - "fInteriorHeadTrackMult", - "fJumpAcrobaticsBase", - "fJumpAcroMultiplier", - "fJumpEncumbranceBase", - "fJumpEncumbranceMultiplier", - "fJumpMoveBase", - "fJumpMoveMult", - "fJumpRunMultiplier", - "fKnockDownMult", - "fLevelMod", - "fLevelUpHealthEndMult", - "fLightMaxMod", - "fLuckMod", - "fMagesGuildTravel", - "fMagicCreatureCastDelay", - "fMagicDetectRefreshRate", - "fMagicItemConstantMult", - "fMagicItemCostMult", - "fMagicItemOnceMult", - "fMagicItemPriceMult", - "fMagicItemRechargePerSecond", - "fMagicItemStrikeMult", - "fMagicItemUsedMult", - "fMagicStartIconBlink", - "fMagicSunBlockedMult", - "fMajorSkillBonus", - "fMaxFlySpeed", - "fMaxHandToHandMult", - "fMaxHeadTrackDistance", - "fMaxWalkSpeed", - "fMaxWalkSpeedCreature", - "fMedMaxMod", - "fMessageTimePerChar", - "fMinFlySpeed", - "fMinHandToHandMult", - "fMinorSkillBonus", - "fMinWalkSpeed", - "fMinWalkSpeedCreature", - "fMiscSkillBonus", - "fNPCbaseMagickaMult", - "fNPCHealthBarFade", - "fNPCHealthBarTime", - "fPCbaseMagickaMult", - "fPerDieRollMult", - "fPersonalityMod", - "fPerTempMult", - "fPickLockMult", - "fPickPocketMod", - "fPotionMinUsefulDuration", - "fPotionStrengthMult", - "fPotionT1DurMult", - "fPotionT1MagMult", - "fPotionT4BaseStrengthMult", - "fPotionT4EquipStrengthMult", - "fProjectileMaxSpeed", - "fProjectileMinSpeed", - "fProjectileThrownStoreChance", - "fRepairAmountMult", - "fRepairMult", - "fReputationMod", - "fRestMagicMult", - "fSeriousWoundMult", - "fSleepRandMod", - "fSleepRestMod", - "fSneakBootMult", - "fSneakDistanceBase", - "fSneakDistanceMultiplier", - "fSneakNoViewMult", - "fSneakSkillMult", - "fSneakSpeedMultiplier", - "fSneakUseDelay", - "fSneakUseDist", - "fSneakViewMult", - "fSoulGemMult", - "fSpecialSkillBonus", - "fSpellMakingValueMult", - "fSpellPriceMult", - "fSpellValueMult", - "fStromWalkMult", - "fStromWindSpeed", - "fSuffocationDamage", - "fSwimHeightScale", - "fSwimRunAthleticsMult", - "fSwimRunBase", - "fSwimWalkAthleticsMult", - "fSwimWalkBase", - "fSwingBlockBase", - "fSwingBlockMult", - "fTargetSpellMaxSpeed", - "fThrownWeaponMaxSpeed", - "fThrownWeaponMinSpeed", - "fTrapCostMult", - "fTravelMult", - "fTravelTimeMult", - "fUnarmoredBase1", - "fUnarmoredBase2", - "fVanityDelay", - "fVoiceIdleOdds", - "fWaterReflectUpdateAlways", - "fWaterReflectUpdateSeldom", - "fWeaponDamageMult", - "fWeaponFatigueBlockMult", - "fWeaponFatigueMult", - "fWereWolfAcrobatics", - "fWereWolfAgility", - "fWereWolfAlchemy", - "fWereWolfAlteration", - "fWereWolfArmorer", - "fWereWolfAthletics", - "fWereWolfAxe", - "fWereWolfBlock", - "fWereWolfBluntWeapon", - "fWereWolfConjuration", - "fWereWolfDestruction", - "fWereWolfEnchant", - "fWereWolfEndurance", - "fWereWolfFatigue", - "fWereWolfHandtoHand", - "fWereWolfHealth", - "fWereWolfHeavyArmor", - "fWereWolfIllusion", - "fWereWolfIntellegence", - "fWereWolfLightArmor", - "fWereWolfLongBlade", - "fWereWolfLuck", - "fWereWolfMagicka", - "fWereWolfMarksman", - "fWereWolfMediumArmor", - "fWereWolfMerchantile", - "fWereWolfMysticism", - "fWereWolfPersonality", - "fWereWolfRestoration", - "fWereWolfRunMult", - "fWereWolfSecurity", - "fWereWolfShortBlade", - "fWereWolfSilverWeaponDamageMult", - "fWereWolfSneak", - "fWereWolfSpear", - "fWereWolfSpeechcraft", - "fWereWolfSpeed", - "fWereWolfStrength", - "fWereWolfUnarmored", - "fWereWolfWillPower", - "fWortChanceValue", - 0 - }; - - static const float gmstFloatsValues[] = - { - 0.3, // fAIFleeFleeMult - 7.0, // fAIFleeHealthMult - 3.0, // fAIMagicSpellMult - 1.0, // fAIMeleeArmorMult - 1.0, // fAIMeleeSummWeaponMult - 2.0, // fAIMeleeWeaponMult - 5.0, // fAIRangeMagicSpellMult - 5.0, // fAIRangeMeleeWeaponMult - 2000.0, // fAlarmRadius - 1.0, // fAthleticsRunBonus - 40.0, // fAudioDefaultMaxDistance - 5.0, // fAudioDefaultMinDistance - 50.0, // fAudioMaxDistanceMult - 20.0, // fAudioMinDistanceMult - 60.0, // fAudioVoiceDefaultMaxDistance - 10.0, // fAudioVoiceDefaultMinDistance - 50.0, // fAutoPCSpellChance - 80.0, // fAutoSpellChance - 50.0, // fBargainOfferBase - -4.0, // fBargainOfferMulti - 24.0, // fBarterGoldResetDelay - 1.75, // fBaseRunMultiplier - 1.25, // fBlockStillBonus - 150.0, // fBribe1000Mod - 75.0, // fBribe100Mod - 35.0, // fBribe10Mod - 60.0, // fCombatAngleXY - 60.0, // fCombatAngleZ - 0.25, // fCombatArmorMinMult - -90.0, // fCombatBlockLeftAngle - 30.0, // fCombatBlockRightAngle - 4.0, // fCombatCriticalStrikeMult - 0.1, // fCombatDelayCreature - 0.1, // fCombatDelayNPC - 128.0, // fCombatDistance - 0.3, // fCombatDistanceWerewolfMod - 30.0, // fCombatForceSideAngle - 0.2, // fCombatInvisoMult - 1.5, // fCombatKODamageMult - 45.0, // fCombatTorsoSideAngle - 0.3, // fCombatTorsoStartPercent - 0.8, // fCombatTorsoStopPercent - 15.0, // fConstantEffectMult - 72.0, // fCorpseClearDelay - 72.0, // fCorpseRespawnDelay - 0.5, // fCrimeGoldDiscountMult - 0.9, // fCrimeGoldTurnInMult - 1.0, // fCrimeStealing - 0.5, // fDamageStrengthBase - 0.1, // fDamageStrengthMult - 5.0, // fDifficultyMult - 2.5, // fDiseaseXferChance - -10.0, // fDispAttacking - -1.0, // fDispBargainFailMod - 1.0, // fDispBargainSuccessMod - 0.0, // fDispCrimeMod - -10.0, // fDispDiseaseMod - 3.0, // fDispFactionMod - 1.0, // fDispFactionRankBase - 0.5, // fDispFactionRankMult - 1.0, // fDispositionMod - 50.0, // fDispPersonalityBase - 0.5, // fDispPersonalityMult - -25.0, // fDispPickPocketMod - 5.0, // fDispRaceMod - -0.5, // fDispStealing - -5.0, // fDispWeaponDrawn - 0.5, // fEffectCostMult - 0.1, // fElementalShieldMult - 3.0, // fEnchantmentChanceMult - 0.5, // fEnchantmentConstantChanceMult - 100.0, // fEnchantmentConstantDurationMult - 0.1, // fEnchantmentMult - 1000.0, // fEnchantmentValueMult - 0.3, // fEncumberedMoveEffect - 5.0, // fEncumbranceStrMult - 0.04, // fEndFatigueMult - 0.25, // fFallAcroBase - 0.01, // fFallAcroMult - 400.0, // fFallDamageDistanceMin - 0.0, // fFallDistanceBase - 0.07, // fFallDistanceMult - 2.0, // fFatigueAttackBase - 0.0, // fFatigueAttackMult - 1.25, // fFatigueBase - 4.0, // fFatigueBlockBase - 0.0, // fFatigueBlockMult - 5.0, // fFatigueJumpBase - 0.0, // fFatigueJumpMult - 0.5, // fFatigueMult - 2.5, // fFatigueReturnBase - 0.02, // fFatigueReturnMult - 5.0, // fFatigueRunBase - 2.0, // fFatigueRunMult - 1.5, // fFatigueSneakBase - 1.5, // fFatigueSneakMult - 0.0, // fFatigueSpellBase - 0.0, // fFatigueSpellCostMult - 0.0, // fFatigueSpellMult - 7.0, // fFatigueSwimRunBase - 0.0, // fFatigueSwimRunMult - 2.5, // fFatigueSwimWalkBase - 0.0, // fFatigueSwimWalkMult - 0.2, // fFightDispMult - 0.005, // fFightDistanceMultiplier - 50.0, // fFightStealing - 3000.0, // fFleeDistance - 512.0, // fGreetDistanceReset - 0.1, // fHandtoHandHealthPer - 1.0, // fHandToHandReach - 0.5, // fHoldBreathEndMult - 20.0, // fHoldBreathTime - 0.75, // fIdleChanceMultiplier - 1.0, // fIngredientMult - 0.5, // fInteriorHeadTrackMult - 128.0, // fJumpAcrobaticsBase - 4.0, // fJumpAcroMultiplier - 0.5, // fJumpEncumbranceBase - 1.0, // fJumpEncumbranceMultiplier - 0.5, // fJumpMoveBase - 0.5, // fJumpMoveMult - 1.0, // fJumpRunMultiplier - 0.5, // fKnockDownMult - 5.0, // fLevelMod - 0.1, // fLevelUpHealthEndMult - 0.6, // fLightMaxMod - 10.0, // fLuckMod - 10.0, // fMagesGuildTravel - 1.5, // fMagicCreatureCastDelay - 0.0167, // fMagicDetectRefreshRate - 1.0, // fMagicItemConstantMult - 1.0, // fMagicItemCostMult - 1.0, // fMagicItemOnceMult - 1.0, // fMagicItemPriceMult - 0.05, // fMagicItemRechargePerSecond - 1.0, // fMagicItemStrikeMult - 1.0, // fMagicItemUsedMult - 3.0, // fMagicStartIconBlink - 0.5, // fMagicSunBlockedMult - 0.75, // fMajorSkillBonus - 300.0, // fMaxFlySpeed - 0.5, // fMaxHandToHandMult - 400.0, // fMaxHeadTrackDistance - 200.0, // fMaxWalkSpeed - 300.0, // fMaxWalkSpeedCreature - 0.9, // fMedMaxMod - 0.1, // fMessageTimePerChar - 5.0, // fMinFlySpeed - 0.1, // fMinHandToHandMult - 1.0, // fMinorSkillBonus - 100.0, // fMinWalkSpeed - 5.0, // fMinWalkSpeedCreature - 1.25, // fMiscSkillBonus - 2.0, // fNPCbaseMagickaMult - 0.5, // fNPCHealthBarFade - 3.0, // fNPCHealthBarTime - 1.0, // fPCbaseMagickaMult - 0.3, // fPerDieRollMult - 5.0, // fPersonalityMod - 1.0, // fPerTempMult - -1.0, // fPickLockMult - 0.3, // fPickPocketMod - 20.0, // fPotionMinUsefulDuration - 0.5, // fPotionStrengthMult - 0.5, // fPotionT1DurMult - 1.5, // fPotionT1MagMult - 20.0, // fPotionT4BaseStrengthMult - 12.0, // fPotionT4EquipStrengthMult - 3000.0, // fProjectileMaxSpeed - 400.0, // fProjectileMinSpeed - 25.0, // fProjectileThrownStoreChance - 3.0, // fRepairAmountMult - 1.0, // fRepairMult - 1.0, // fReputationMod - 0.15, // fRestMagicMult - 0.0, // fSeriousWoundMult - 0.25, // fSleepRandMod - 0.3, // fSleepRestMod - -1.0, // fSneakBootMult - 0.5, // fSneakDistanceBase - 0.002, // fSneakDistanceMultiplier - 0.5, // fSneakNoViewMult - 1.0, // fSneakSkillMult - 0.75, // fSneakSpeedMultiplier - 1.0, // fSneakUseDelay - 500.0, // fSneakUseDist - 1.5, // fSneakViewMult - 3.0, // fSoulGemMult - 0.8, // fSpecialSkillBonus - 7.0, // fSpellMakingValueMult - 2.0, // fSpellPriceMult - 10.0, // fSpellValueMult - 0.25, // fStromWalkMult - 0.7, // fStromWindSpeed - 3.0, // fSuffocationDamage - 0.9, // fSwimHeightScale - 0.1, // fSwimRunAthleticsMult - 0.5, // fSwimRunBase - 0.02, // fSwimWalkAthleticsMult - 0.5, // fSwimWalkBase - 1.0, // fSwingBlockBase - 1.0, // fSwingBlockMult - 1000.0, // fTargetSpellMaxSpeed - 1000.0, // fThrownWeaponMaxSpeed - 300.0, // fThrownWeaponMinSpeed - 0.0, // fTrapCostMult - 4000.0, // fTravelMult - 16000.0,// fTravelTimeMult - 0.1, // fUnarmoredBase1 - 0.065, // fUnarmoredBase2 - 30.0, // fVanityDelay - 10.0, // fVoiceIdleOdds - 0.0, // fWaterReflectUpdateAlways - 10.0, // fWaterReflectUpdateSeldom - 0.1, // fWeaponDamageMult - 1.0, // fWeaponFatigueBlockMult - 0.25, // fWeaponFatigueMult - 150.0, // fWereWolfAcrobatics - 150.0, // fWereWolfAgility - 1.0, // fWereWolfAlchemy - 1.0, // fWereWolfAlteration - 1.0, // fWereWolfArmorer - 150.0, // fWereWolfAthletics - 1.0, // fWereWolfAxe - 1.0, // fWereWolfBlock - 1.0, // fWereWolfBluntWeapon - 1.0, // fWereWolfConjuration - 1.0, // fWereWolfDestruction - 1.0, // fWereWolfEnchant - 150.0, // fWereWolfEndurance - 400.0, // fWereWolfFatigue - 100.0, // fWereWolfHandtoHand - 2.0, // fWereWolfHealth - 1.0, // fWereWolfHeavyArmor - 1.0, // fWereWolfIllusion - 1.0, // fWereWolfIntellegence - 1.0, // fWereWolfLightArmor - 1.0, // fWereWolfLongBlade - 1.0, // fWereWolfLuck - 100.0, // fWereWolfMagicka - 1.0, // fWereWolfMarksman - 1.0, // fWereWolfMediumArmor - 1.0, // fWereWolfMerchantile - 1.0, // fWereWolfMysticism - 1.0, // fWereWolfPersonality - 1.0, // fWereWolfRestoration - 1.5, // fWereWolfRunMult - 1.0, // fWereWolfSecurity - 1.0, // fWereWolfShortBlade - 1.5, // fWereWolfSilverWeaponDamageMult - 1.0, // fWereWolfSneak - 1.0, // fWereWolfSpear - 1.0, // fWereWolfSpeechcraft - 150.0, // fWereWolfSpeed - 150.0, // fWereWolfStrength - 100.0, // fWereWolfUnarmored - 1.0, // fWereWolfWillPower - 15.0, // fWortChanceValue - }; - - static const char *gmstIntegers[] = - { - "i1stPersonSneakDelta", - "iAlarmAttack", - "iAlarmKilling", - "iAlarmPickPocket", - "iAlarmStealing", - "iAlarmTresspass", - "iAlchemyMod", - "iAutoPCSpellMax", - "iAutoRepFacMod", - "iAutoRepLevMod", - "iAutoSpellAlterationMax", - "iAutoSpellAttSkillMin", - "iAutoSpellConjurationMax", - "iAutoSpellDestructionMax", - "iAutoSpellIllusionMax", - "iAutoSpellMysticismMax", - "iAutoSpellRestorationMax", - "iAutoSpellTimesCanCast", - "iBarterFailDisposition", - "iBarterSuccessDisposition", - "iBaseArmorSkill", - "iBlockMaxChance", - "iBlockMinChance", - "iBootsWeight", - "iCrimeAttack", - "iCrimeKilling", - "iCrimePickPocket", - "iCrimeThreshold", - "iCrimeThresholdMultiplier", - "iCrimeTresspass", - "iCuirassWeight", - "iDaysinPrisonMod", - "iDispAttackMod", - "iDispKilling", - "iDispTresspass", - "iFightAlarmMult", - "iFightAttack", - "iFightAttacking", - "iFightDistanceBase", - "iFightKilling", - "iFightPickpocket", - "iFightTrespass", - "iFlee", - "iGauntletWeight", - "iGreavesWeight", - "iGreetDistanceMultiplier", - "iGreetDuration", - "iHelmWeight", - "iKnockDownOddsBase", - "iKnockDownOddsMult", - "iLevelUp01Mult", - "iLevelUp02Mult", - "iLevelUp03Mult", - "iLevelUp04Mult", - "iLevelUp05Mult", - "iLevelUp06Mult", - "iLevelUp07Mult", - "iLevelUp08Mult", - "iLevelUp09Mult", - "iLevelUp10Mult", - "iLevelupMajorMult", - "iLevelupMajorMultAttribute", - "iLevelupMinorMult", - "iLevelupMinorMultAttribute", - "iLevelupMiscMultAttriubte", - "iLevelupSpecialization", - "iLevelupTotal", - "iMagicItemChargeConst", - "iMagicItemChargeOnce", - "iMagicItemChargeStrike", - "iMagicItemChargeUse", - "iMaxActivateDist", - "iMaxInfoDist", - "iMonthsToRespawn", - "iNumberCreatures", - "iPauldronWeight", - "iPerMinChance", - "iPerMinChange", - "iPickMaxChance", - "iPickMinChance", - "iShieldWeight", - "iSoulAmountForConstantEffect", - "iTrainingMod", - "iVoiceAttackOdds", - "iVoiceHitOdds", - "iWereWolfBounty", - "iWereWolfFightMod", - "iWereWolfFleeMod", - "iWereWolfLevelToAttack", - 0 - }; - - static const int gmstIntegersValues[] = - { - 10, // i1stPersonSneakDelta - 50, // iAlarmAttack - 90, // iAlarmKilling - 20, // iAlarmPickPocket - 1, // iAlarmStealing - 5, // iAlarmTresspass - 2, // iAlchemyMod - 100, // iAutoPCSpellMax - 2, // iAutoRepFacMod - 0, // iAutoRepLevMod - 5, // iAutoSpellAlterationMax - 70, // iAutoSpellAttSkillMin - 2, // iAutoSpellConjurationMax - 5, // iAutoSpellDestructionMax - 5, // iAutoSpellIllusionMax - 5, // iAutoSpellMysticismMax - 5, // iAutoSpellRestorationMax - 3, // iAutoSpellTimesCanCast - -1, // iBarterFailDisposition - 1, // iBarterSuccessDisposition - 30, // iBaseArmorSkill - 50, // iBlockMaxChance - 10, // iBlockMinChance - 20, // iBootsWeight - 40, // iCrimeAttack - 1000, // iCrimeKilling - 25, // iCrimePickPocket - 1000, // iCrimeThreshold - 10, // iCrimeThresholdMultiplier - 5, // iCrimeTresspass - 30, // iCuirassWeight - 100, // iDaysinPrisonMod - -50, // iDispAttackMod - -50, // iDispKilling - -20, // iDispTresspass - 1, // iFightAlarmMult - 100, // iFightAttack - 50, // iFightAttacking - 20, // iFightDistanceBase - 50, // iFightKilling - 25, // iFightPickpocket - 25, // iFightTrespass - 0, // iFlee - 5, // iGauntletWeight - 15, // iGreavesWeight - 6, // iGreetDistanceMultiplier - 4, // iGreetDuration - 5, // iHelmWeight - 50, // iKnockDownOddsBase - 50, // iKnockDownOddsMult - 2, // iLevelUp01Mult - 2, // iLevelUp02Mult - 2, // iLevelUp03Mult - 2, // iLevelUp04Mult - 3, // iLevelUp05Mult - 3, // iLevelUp06Mult - 3, // iLevelUp07Mult - 4, // iLevelUp08Mult - 4, // iLevelUp09Mult - 5, // iLevelUp10Mult - 1, // iLevelupMajorMult - 1, // iLevelupMajorMultAttribute - 1, // iLevelupMinorMult - 1, // iLevelupMinorMultAttribute - 1, // iLevelupMiscMultAttriubte - 1, // iLevelupSpecialization - 10, // iLevelupTotal - 10, // iMagicItemChargeConst - 1, // iMagicItemChargeOnce - 10, // iMagicItemChargeStrike - 5, // iMagicItemChargeUse - 192, // iMaxActivateDist - 192, // iMaxInfoDist - 4, // iMonthsToRespawn - 1, // iNumberCreatures - 10, // iPauldronWeight - 5, // iPerMinChance - 10, // iPerMinChange - 75, // iPickMaxChance - 5, // iPickMinChance - 15, // iShieldWeight - 400, // iSoulAmountForConstantEffect - 10, // iTrainingMod - 10, // iVoiceAttackOdds - 30, // iVoiceHitOdds - 10000, // iWereWolfBounty - 100, // iWereWolfFightMod - 100, // iWereWolfFleeMod - 20, // iWereWolfLevelToAttack - }; - - static const char *gmstStrings[] = - { - "s3dAudio", - "s3dHardware", - "s3dSoftware", - "sAbsorb", - "sAcrobat", - "sActivate", - "sActivateXbox", - "sActorInCombat", - "sAdmire", - "sAdmireFail", - "sAdmireSuccess", - "sAgent", - "sAgiDesc", - "sAIDistance", - "sAlembic", - "sAllTab", - "sAlways", - "sAlways_Run", - "sand", - "sApparatus", - "sApparelTab", - "sArcher", - "sArea", - "sAreaDes", - "sArmor", - "sArmorRating", - "sAsk", - "sAssassin", - "sAt", - "sAttack", - "sAttributeAgility", - "sAttributeEndurance", - "sAttributeIntelligence", - "sAttributeListTitle", - "sAttributeLuck", - "sAttributePersonality", - "sAttributesMenu1", - "sAttributeSpeed", - "sAttributeStrength", - "sAttributeWillpower", - "sAudio", - "sAuto_Run", - "sBack", - "sBackspace", - "sBackXbox", - "sBarbarian", - "sBard", - "sBarter", - "sBarterDialog1", - "sBarterDialog10", - "sBarterDialog11", - "sBarterDialog12", - "sBarterDialog2", - "sBarterDialog3", - "sBarterDialog4", - "sBarterDialog5", - "sBarterDialog6", - "sBarterDialog7", - "sBarterDialog8", - "sBarterDialog9", - "sBattlemage", - "sBestAttack", - "sBirthSign", - "sBirthsignmenu1", - "sBirthsignmenu2", - "sBlocks", - "sBonusSkillTitle", - "sBookPageOne", - "sBookPageTwo", - "sBookSkillMessage", - "sBounty", - "sBreath", - "sBribe 10 Gold", - "sBribe 100 Gold", - "sBribe 1000 Gold", - "sBribeFail", - "sBribeSuccess", - "sBuy", - "sBye", - "sCalcinator", - "sCancel", - "sCantEquipWeapWarning", - "sCastCost", - "sCaughtStealingMessage", - "sCenter", - "sChangedMastersMsg", - "sCharges", - "sChooseClassMenu1", - "sChooseClassMenu2", - "sChooseClassMenu3", - "sChooseClassMenu4", - "sChop", - "sClass", - "sClassChoiceMenu1", - "sClassChoiceMenu2", - "sClassChoiceMenu3", - "sClose", - "sCompanionShare", - "sCompanionWarningButtonOne", - "sCompanionWarningButtonTwo", - "sCompanionWarningMessage", - "sCondition", - "sConsoleTitle", - "sContainer", - "sContentsMessage1", - "sContentsMessage2", - "sContentsMessage3", - "sControlerVibration", - "sControls", - "sControlsMenu1", - "sControlsMenu2", - "sControlsMenu3", - "sControlsMenu4", - "sControlsMenu5", - "sControlsMenu6", - "sCostChance", - "sCostCharge", - "sCreate", - "sCreateClassMenu1", - "sCreateClassMenu2", - "sCreateClassMenu3", - "sCreateClassMenuHelp1", - "sCreateClassMenuHelp2", - "sCreateClassMenuWarning", - "sCreatedEffects", - "sCrimeHelp", - "sCrimeMessage", - "sCrouch_Sneak", - "sCrouchXbox", - "sCrusader", - "sCursorOff", - "sCustom", - "sCustomClassName", - "sDamage", - "sDark_Gamma", - "sDay", - "sDefaultCellname", - "sDelete", - "sDeleteGame", - "sDeleteNote", - "sDeleteSpell", - "sDeleteSpellError", - "sDetail_Level", - "sDialogMenu1", - "sDialogText1Xbox", - "sDialogText2Xbox", - "sDialogText3Xbox", - "sDifficulty", - "sDisposeCorpseFail", - "sDisposeofCorpse", - "sDone", - "sDoYouWantTo", - "sDrain", - "sDrop", - "sDuration", - "sDurationDes", - "sEasy", - "sEditNote", - "sEffectAbsorbAttribute", - "sEffectAbsorbFatigue", - "sEffectAbsorbHealth", - "sEffectAbsorbSkill", - "sEffectAbsorbSpellPoints", - "sEffectAlmsiviIntervention", - "sEffectBlind", - "sEffectBoundBattleAxe", - "sEffectBoundBoots", - "sEffectBoundCuirass", - "sEffectBoundDagger", - "sEffectBoundGloves", - "sEffectBoundHelm", - "sEffectBoundLongbow", - "sEffectBoundLongsword", - "sEffectBoundMace", - "sEffectBoundShield", - "sEffectBoundSpear", - "sEffectBurden", - "sEffectCalmCreature", - "sEffectCalmHumanoid", - "sEffectChameleon", - "sEffectCharm", - "sEffectCommandCreatures", - "sEffectCommandHumanoids", - "sEffectCorpus", - "sEffectCureBlightDisease", - "sEffectCureCommonDisease", - "sEffectCureCorprusDisease", - "sEffectCureParalyzation", - "sEffectCurePoison", - "sEffectDamageAttribute", - "sEffectDamageFatigue", - "sEffectDamageHealth", - "sEffectDamageMagicka", - "sEffectDamageSkill", - "sEffectDemoralizeCreature", - "sEffectDemoralizeHumanoid", - "sEffectDetectAnimal", - "sEffectDetectEnchantment", - "sEffectDetectKey", - "sEffectDisintegrateArmor", - "sEffectDisintegrateWeapon", - "sEffectDispel", - "sEffectDivineIntervention", - "sEffectDrainAttribute", - "sEffectDrainFatigue", - "sEffectDrainHealth", - "sEffectDrainSkill", - "sEffectDrainSpellpoints", - "sEffectExtraSpell", - "sEffectFeather", - "sEffectFireDamage", - "sEffectFireShield", - "sEffectFortifyAttackBonus", - "sEffectFortifyAttribute", - "sEffectFortifyFatigue", - "sEffectFortifyHealth", - "sEffectFortifyMagickaMultiplier", - "sEffectFortifySkill", - "sEffectFortifySpellpoints", - "sEffectFrenzyCreature", - "sEffectFrenzyHumanoid", - "sEffectFrostDamage", - "sEffectFrostShield", - "sEffectInvisibility", - "sEffectJump", - "sEffectLevitate", - "sEffectLight", - "sEffectLightningShield", - "sEffectLock", - "sEffectMark", - "sEffectNightEye", - "sEffectOpen", - "sEffectParalyze", - "sEffectPoison", - "sEffectRallyCreature", - "sEffectRallyHumanoid", - "sEffectRecall", - "sEffectReflect", - "sEffectRemoveCurse", - "sEffectResistBlightDisease", - "sEffectResistCommonDisease", - "sEffectResistCorprusDisease", - "sEffectResistFire", - "sEffectResistFrost", - "sEffectResistMagicka", - "sEffectResistNormalWeapons", - "sEffectResistParalysis", - "sEffectResistPoison", - "sEffectResistShock", - "sEffectRestoreAttribute", - "sEffectRestoreFatigue", - "sEffectRestoreHealth", - "sEffectRestoreSkill", - "sEffectRestoreSpellPoints", - "sEffects", - "sEffectSanctuary", - "sEffectShield", - "sEffectShockDamage", - "sEffectSilence", - "sEffectSlowFall", - "sEffectSoultrap", - "sEffectSound", - "sEffectSpellAbsorption", - "sEffectStuntedMagicka", - "sEffectSummonAncestralGhost", - "sEffectSummonBonelord", - "sEffectSummonCenturionSphere", - "sEffectSummonClannfear", - "sEffectSummonCreature01", - "sEffectSummonCreature02", - "sEffectSummonCreature03", - "sEffectSummonCreature04", - "sEffectSummonCreature05", - "sEffectSummonDaedroth", - "sEffectSummonDremora", - "sEffectSummonFabricant", - "sEffectSummonFlameAtronach", - "sEffectSummonFrostAtronach", - "sEffectSummonGoldensaint", - "sEffectSummonGreaterBonewalker", - "sEffectSummonHunger", - "sEffectSummonLeastBonewalker", - "sEffectSummonScamp", - "sEffectSummonSkeletalMinion", - "sEffectSummonStormAtronach", - "sEffectSummonWingedTwilight", - "sEffectSunDamage", - "sEffectSwiftSwim", - "sEffectTelekinesis", - "sEffectTurnUndead", - "sEffectVampirism", - "sEffectWaterBreathing", - "sEffectWaterWalking", - "sEffectWeaknessToBlightDisease", - "sEffectWeaknessToCommonDisease", - "sEffectWeaknessToCorprusDisease", - "sEffectWeaknessToFire", - "sEffectWeaknessToFrost", - "sEffectWeaknessToMagicka", - "sEffectWeaknessToNormalWeapons", - "sEffectWeaknessToPoison", - "sEffectWeaknessToShock", - "sEnableJoystick", - "sEnchanting", - "sEnchantItems", - "sEnchantmentHelp1", - "sEnchantmentHelp10", - "sEnchantmentHelp2", - "sEnchantmentHelp3", - "sEnchantmentHelp4", - "sEnchantmentHelp5", - "sEnchantmentHelp6", - "sEnchantmentHelp7", - "sEnchantmentHelp8", - "sEnchantmentHelp9", - "sEnchantmentMenu1", - "sEnchantmentMenu10", - "sEnchantmentMenu11", - "sEnchantmentMenu12", - "sEnchantmentMenu2", - "sEnchantmentMenu3", - "sEnchantmentMenu4", - "sEnchantmentMenu5", - "sEnchantmentMenu6", - "sEnchantmentMenu7", - "sEnchantmentMenu8", - "sEnchantmentMenu9", - "sEncumbrance", - "sEndDesc", - "sEquip", - "sExitGame", - "sExpelled", - "sExpelledMessage", - "sFace", - "sFaction", - "sFar", - "sFast", - "sFatDesc", - "sFatigue", - "sFavoriteSkills", - "sfeet", - "sFileSize", - "sfootarea", - "sFootsteps", - "sfor", - "sFortify", - "sForward", - "sForwardXbox", - "sFull", - "sGame", - "sGameWithoutLauncherXbox", - "sGamma_Correction", - "sGeneralMastPlugMismatchMsg", - "sGold", - "sGoodbye", - "sGoverningAttribute", - "sgp", - "sHair", - "sHard", - "sHeal", - "sHealer", - "sHealth", - "sHealthDesc", - "sHealthPerHourOfRest", - "sHealthPerLevel", - "sHeavy", - "sHigh", - "sin", - "sInfo", - "sInfoRefusal", - "sIngredients", - "sInPrisonTitle", - "sInputMenu1", - "sIntDesc", - "sIntimidate", - "sIntimidateFail", - "sIntimidateSuccess", - "sInvalidSaveGameMsg", - "sInvalidSaveGameMsgXBOX", - "sInventory", - "sInventoryMenu1", - "sInventoryMessage1", - "sInventoryMessage2", - "sInventoryMessage3", - "sInventoryMessage4", - "sInventoryMessage5", - "sInventorySelectNoIngredients", - "sInventorySelectNoItems", - "sInventorySelectNoSoul", - "sItem", - "sItemCastConstant", - "sItemCastOnce", - "sItemCastWhenStrikes", - "sItemCastWhenUsed", - "sItemName", - "sJournal", - "sJournalCmd", - "sJournalEntry", - "sJournalXbox", - "sJoystickHatShort", - "sJoystickNotFound", - "sJoystickShort", - "sJump", - "sJumpXbox", - "sKeyName_00", - "sKeyName_01", - "sKeyName_02", - "sKeyName_03", - "sKeyName_04", - "sKeyName_05", - "sKeyName_06", - "sKeyName_07", - "sKeyName_08", - "sKeyName_09", - "sKeyName_0A", - "sKeyName_0B", - "sKeyName_0C", - "sKeyName_0D", - "sKeyName_0E", - "sKeyName_0F", - "sKeyName_10", - "sKeyName_11", - "sKeyName_12", - "sKeyName_13", - "sKeyName_14", - "sKeyName_15", - "sKeyName_16", - "sKeyName_17", - "sKeyName_18", - "sKeyName_19", - "sKeyName_1A", - "sKeyName_1B", - "sKeyName_1C", - "sKeyName_1D", - "sKeyName_1E", - "sKeyName_1F", - "sKeyName_20", - "sKeyName_21", - "sKeyName_22", - "sKeyName_23", - "sKeyName_24", - "sKeyName_25", - "sKeyName_26", - "sKeyName_27", - "sKeyName_28", - "sKeyName_29", - "sKeyName_2A", - "sKeyName_2B", - "sKeyName_2C", - "sKeyName_2D", - "sKeyName_2E", - "sKeyName_2F", - "sKeyName_30", - "sKeyName_31", - "sKeyName_32", - "sKeyName_33", - "sKeyName_34", - "sKeyName_35", - "sKeyName_36", - "sKeyName_37", - "sKeyName_38", - "sKeyName_39", - "sKeyName_3A", - "sKeyName_3B", - "sKeyName_3C", - "sKeyName_3D", - "sKeyName_3E", - "sKeyName_3F", - "sKeyName_40", - "sKeyName_41", - "sKeyName_42", - "sKeyName_43", - "sKeyName_44", - "sKeyName_45", - "sKeyName_46", - "sKeyName_47", - "sKeyName_48", - "sKeyName_49", - "sKeyName_4A", - "sKeyName_4B", - "sKeyName_4C", - "sKeyName_4D", - "sKeyName_4E", - "sKeyName_4F", - "sKeyName_50", - "sKeyName_51", - "sKeyName_52", - "sKeyName_53", - "sKeyName_54", - "sKeyName_55", - "sKeyName_56", - "sKeyName_57", - "sKeyName_58", - "sKeyName_59", - "sKeyName_5A", - "sKeyName_5B", - "sKeyName_5C", - "sKeyName_5D", - "sKeyName_5E", - "sKeyName_5F", - "sKeyName_60", - "sKeyName_61", - "sKeyName_62", - "sKeyName_63", - "sKeyName_64", - "sKeyName_65", - "sKeyName_66", - "sKeyName_67", - "sKeyName_68", - "sKeyName_69", - "sKeyName_6A", - "sKeyName_6B", - "sKeyName_6C", - "sKeyName_6D", - "sKeyName_6E", - "sKeyName_6F", - "sKeyName_70", - "sKeyName_71", - "sKeyName_72", - "sKeyName_73", - "sKeyName_74", - "sKeyName_75", - "sKeyName_76", - "sKeyName_77", - "sKeyName_78", - "sKeyName_79", - "sKeyName_7A", - "sKeyName_7B", - "sKeyName_7C", - "sKeyName_7D", - "sKeyName_7E", - "sKeyName_7F", - "sKeyName_80", - "sKeyName_81", - "sKeyName_82", - "sKeyName_83", - "sKeyName_84", - "sKeyName_85", - "sKeyName_86", - "sKeyName_87", - "sKeyName_88", - "sKeyName_89", - "sKeyName_8A", - "sKeyName_8B", - "sKeyName_8C", - "sKeyName_8D", - "sKeyName_8E", - "sKeyName_8F", - "sKeyName_90", - "sKeyName_91", - "sKeyName_92", - "sKeyName_93", - "sKeyName_94", - "sKeyName_95", - "sKeyName_96", - "sKeyName_97", - "sKeyName_98", - "sKeyName_99", - "sKeyName_9A", - "sKeyName_9B", - "sKeyName_9C", - "sKeyName_9D", - "sKeyName_9E", - "sKeyName_9F", - "sKeyName_A0", - "sKeyName_A1", - "sKeyName_A2", - "sKeyName_A3", - "sKeyName_A4", - "sKeyName_A5", - "sKeyName_A6", - "sKeyName_A7", - "sKeyName_A8", - "sKeyName_A9", - "sKeyName_AA", - "sKeyName_AB", - "sKeyName_AC", - "sKeyName_AD", - "sKeyName_AE", - "sKeyName_AF", - "sKeyName_B0", - "sKeyName_B1", - "sKeyName_B2", - "sKeyName_B3", - "sKeyName_B4", - "sKeyName_B5", - "sKeyName_B6", - "sKeyName_B7", - "sKeyName_B8", - "sKeyName_B9", - "sKeyName_BA", - "sKeyName_BB", - "sKeyName_BC", - "sKeyName_BD", - "sKeyName_BE", - "sKeyName_BF", - "sKeyName_C0", - "sKeyName_C1", - "sKeyName_C2", - "sKeyName_C3", - "sKeyName_C4", - "sKeyName_C5", - "sKeyName_C6", - "sKeyName_C7", - "sKeyName_C8", - "sKeyName_C9", - "sKeyName_CA", - "sKeyName_CB", - "sKeyName_CC", - "sKeyName_CD", - "sKeyName_CE", - "sKeyName_CF", - "sKeyName_D0", - "sKeyName_D1", - "sKeyName_D2", - "sKeyName_D3", - "sKeyName_D4", - "sKeyName_D5", - "sKeyName_D6", - "sKeyName_D7", - "sKeyName_D8", - "sKeyName_D9", - "sKeyName_DA", - "sKeyName_DB", - "sKeyName_DC", - "sKeyName_DD", - "sKeyName_DE", - "sKeyName_DF", - "sKeyName_E0", - "sKeyName_E1", - "sKeyName_E2", - "sKeyName_E3", - "sKeyName_E4", - "sKeyName_E5", - "sKeyName_E6", - "sKeyName_E7", - "sKeyName_E8", - "sKeyName_E9", - "sKeyName_EA", - "sKeyName_EB", - "sKeyName_EC", - "sKeyName_ED", - "sKeyName_EE", - "sKeyName_EF", - "sKeyName_F0", - "sKeyName_F1", - "sKeyName_F2", - "sKeyName_F3", - "sKeyName_F4", - "sKeyName_F5", - "sKeyName_F6", - "sKeyName_F7", - "sKeyName_F8", - "sKeyName_F9", - "sKeyName_FA", - "sKeyName_FB", - "sKeyName_FC", - "sKeyName_FD", - "sKeyName_FE", - "sKeyName_FF", - "sKeyUsed", - "sKilledEssential", - "sKnight", - "sLeft", - "sLess", - "sLevel", - "sLevelProgress", - "sLevels", - "sLevelUp", - "sLevelUpMenu1", - "sLevelUpMenu2", - "sLevelUpMenu3", - "sLevelUpMenu4", - "sLevelUpMsg", - "sLevitateDisabled", - "sLight", - "sLight_Gamma", - "sLoadFailedMessage", - "sLoadGame", - "sLoadingErrorsMsg", - "sLoadingMessage1", - "sLoadingMessage14", - "sLoadingMessage15", - "sLoadingMessage2", - "sLoadingMessage3", - "sLoadingMessage4", - "sLoadingMessage5", - "sLoadingMessage9", - "sLoadLastSaveMsg", - "sLocal", - "sLockFail", - "sLockImpossible", - "sLockLevel", - "sLockSuccess", - "sLookDownXbox", - "sLookUpXbox", - "sLow", - "sLucDesc", - "sMagDesc", - "sMage", - "sMagic", - "sMagicAncestralGhostID", - "sMagicBonelordID", - "sMagicBoundBattleAxeID", - "sMagicBoundBootsID", - "sMagicBoundCuirassID", - "sMagicBoundDaggerID", - "sMagicBoundHelmID", - "sMagicBoundLeftGauntletID", - "sMagicBoundLongbowID", - "sMagicBoundLongswordID", - "sMagicBoundMaceID", - "sMagicBoundRightGauntletID", - "sMagicBoundShieldID", - "sMagicBoundSpearID", - "sMagicCannotRecast", - "sMagicCenturionSphereID", - "sMagicClannfearID", - "sMagicContractDisease", - "sMagicCorprusWorsens", - "sMagicCreature01ID", - "sMagicCreature02ID", - "sMagicCreature03ID", - "sMagicCreature04ID", - "sMagicCreature05ID", - "sMagicDaedrothID", - "sMagicDremoraID", - "sMagicEffects", - "sMagicFabricantID", - "sMagicFlameAtronachID", - "sMagicFrostAtronachID", - "sMagicGoldenSaintID", - "sMagicGreaterBonewalkerID", - "sMagicHungerID", - "sMagicInsufficientCharge", - "sMagicInsufficientSP", - "sMagicInvalidEffect", - "sMagicInvalidTarget", - "sMagicItem", - "sMagicLeastBonewalkerID", - "sMagicLockSuccess", - "sMagicMenu", - "sMagicOpenSuccess", - "sMagicPCResisted", - "sMagicScampID", - "sMagicSelectTitle", - "sMagicSkeletalMinionID", - "sMagicSkillFail", - "sMagicStormAtronachID", - "sMagicTab", - "sMagicTargetResisted", - "sMagicTargetResistsWeapons", - "sMagicWingedTwilightID", - "sMagnitude", - "sMagnitudeDes", - "sMake", - "sMap", - "sMaster", - "sMastPlugMismatchMsg", - "sMaximumSaveGameMessage", - "sMaxSale", - "sMedium", - "sMenu_Help_Delay", - "sMenu_Mode", - "sMenuModeXbox", - "sMenuNextXbox", - "sMenuPrevXbox", - "sMenus", - "sMessage1", - "sMessage2", - "sMessage3", - "sMessage4", - "sMessage5", - "sMessageQuestionAnswer1", - "sMessageQuestionAnswer2", - "sMessageQuestionAnswer3", - "sMiscTab", - "sMissingMastersMsg", - "sMonk", - "sMonthEveningstar", - "sMonthFirstseed", - "sMonthFrostfall", - "sMonthHeartfire", - "sMonthLastseed", - "sMonthMidyear", - "sMonthMorningstar", - "sMonthRainshand", - "sMonthSecondseed", - "sMonthSunsdawn", - "sMonthSunsdusk", - "sMonthSunsheight", - "sMore", - "sMortar", - "sMouse", - "sMouseFlip", - "sMouseWheelDownShort", - "sMouseWheelUpShort", - "sMove", - "sMoveDownXbox", - "sMoveUpXbox", - "sMusic", - "sName", - "sNameTitle", - "sNear", - "sNeedOneSkill", - "sNeedTwoSkills", - "sNewGame", - "sNext", - "sNextRank", - "sNextSpell", - "sNextSpellXbox", - "sNextWeapon", - "sNextWeaponXbox", - "sNightblade", - "sNo", - "sNoName", - "sNone", - "sNotifyMessage1", - "sNotifyMessage10", - "sNotifyMessage11", - "sNotifyMessage12", - "sNotifyMessage13", - "sNotifyMessage14", - "sNotifyMessage15", - "sNotifyMessage16", - "sNotifyMessage16_a", - "sNotifyMessage17", - "sNotifyMessage18", - "sNotifyMessage19", - "sNotifyMessage2", - "sNotifyMessage20", - "sNotifyMessage21", - "sNotifyMessage22", - "sNotifyMessage23", - "sNotifyMessage24", - "sNotifyMessage25", - "sNotifyMessage26", - "sNotifyMessage27", - "sNotifyMessage28", - "sNotifyMessage29", - "sNotifyMessage3", - "sNotifyMessage30", - "sNotifyMessage31", - "sNotifyMessage32", - "sNotifyMessage33", - "sNotifyMessage34", - "sNotifyMessage35", - "sNotifyMessage36", - "sNotifyMessage37", - "sNotifyMessage38", - "sNotifyMessage39", - "sNotifyMessage4", - "sNotifyMessage40", - "sNotifyMessage41", - "sNotifyMessage42", - "sNotifyMessage43", - "sNotifyMessage44", - "sNotifyMessage45", - "sNotifyMessage46", - "sNotifyMessage47", - "sNotifyMessage48", - "sNotifyMessage49", - "sNotifyMessage4XBOX", - "sNotifyMessage5", - "sNotifyMessage50", - "sNotifyMessage51", - "sNotifyMessage52", - "sNotifyMessage53", - "sNotifyMessage54", - "sNotifyMessage55", - "sNotifyMessage56", - "sNotifyMessage57", - "sNotifyMessage58", - "sNotifyMessage59", - "sNotifyMessage6", - "sNotifyMessage60", - "sNotifyMessage61", - "sNotifyMessage62", - "sNotifyMessage63", - "sNotifyMessage64", - "sNotifyMessage65", - "sNotifyMessage66", - "sNotifyMessage67", - "sNotifyMessage6a", - "sNotifyMessage7", - "sNotifyMessage8", - "sNotifyMessage9", - "sOff", - "sOffer", - "sOfferMenuTitle", - "sOK", - "sOn", - "sOnce", - "sOneHanded", - "sOnetypeEffectMessage", - "sonword", - "sOptions", - "sOptionsMenuXbox", - "spercent", - "sPerDesc", - "sPersuasion", - "sPersuasionMenuTitle", - "sPickUp", - "sPilgrim", - "spoint", - "spoints", - "sPotionSuccess", - "sPowerAlreadyUsed", - "sPowers", - "sPreferences", - "sPrefs", - "sPrev", - "sPrevSpell", - "sPrevSpellXbox", - "sPrevWeapon", - "sPrevWeaponXbox", - "sProfitValue", - "sQuality", - "sQuanityMenuMessage01", - "sQuanityMenuMessage02", - "sQuestionDeleteSpell", - "sQuestionMark", - "sQuick0Xbox", - "sQuick10Cmd", - "sQuick1Cmd", - "sQuick2Cmd", - "sQuick3Cmd", - "sQuick4Cmd", - "sQuick4Xbox", - "sQuick5Cmd", - "sQuick5Xbox", - "sQuick6Cmd", - "sQuick6Xbox", - "sQuick7Cmd", - "sQuick7Xbox", - "sQuick8Cmd", - "sQuick8Xbox", - "sQuick9Cmd", - "sQuick9Xbox", - "sQuick_Save", - "sQuickLoadCmd", - "sQuickLoadXbox", - "sQuickMenu", - "sQuickMenu1", - "sQuickMenu2", - "sQuickMenu3", - "sQuickMenu4", - "sQuickMenu5", - "sQuickMenu6", - "sQuickMenuInstruc", - "sQuickMenuTitle", - "sQuickSaveCmd", - "sQuickSaveXbox", - "sRace", - "sRaceMenu1", - "sRaceMenu2", - "sRaceMenu3", - "sRaceMenu4", - "sRaceMenu5", - "sRaceMenu6", - "sRaceMenu7", - "sRacialTraits", - "sRange", - "sRangeDes", - "sRangeSelf", - "sRangeTarget", - "sRangeTouch", - "sReady_Magic", - "sReady_Weapon", - "sReadyItemXbox", - "sReadyMagicXbox", - "sRechargeEnchantment", - "sRender_Distance", - "sRepair", - "sRepairFailed", - "sRepairServiceTitle", - "sRepairSuccess", - "sReputation", - "sResChangeWarning", - "sRest", - "sRestIllegal", - "sRestKey", - "sRestMenu1", - "sRestMenu2", - "sRestMenu3", - "sRestMenu4", - "sRestMenuXbox", - "sRestore", - "sRetort", - "sReturnToGame", - "sRight", - "sRogue", - "sRun", - "sRunXbox", - "sSave", - "sSaveGame", - "sSaveGameDenied", - "sSaveGameFailed", - "sSaveGameNoMemory", - "sSaveGameTooBig", - "sSaveMenu1", - "sSaveMenuHelp01", - "sSaveMenuHelp02", - "sSaveMenuHelp03", - "sSaveMenuHelp04", - "sSaveMenuHelp05", - "sSaveMenuHelp06", - "sSchool", - "sSchoolAlteration", - "sSchoolConjuration", - "sSchoolDestruction", - "sSchoolIllusion", - "sSchoolMysticism", - "sSchoolRestoration", - "sScout", - "sScrolldown", - "sScrollup", - "ssecond", - "sseconds", - "sSeldom", - "sSelect", - "sSell", - "sSellerGold", - "sService", - "sServiceRefusal", - "sServiceRepairTitle", - "sServiceSpellsTitle", - "sServiceTrainingTitle", - "sServiceTrainingWords", - "sServiceTravelTitle", - "sSetValueMessage01", - "sSex", - "sShadows", - "sShadowText", - "sShift", - "sSkill", - "sSkillAcrobatics", - "sSkillAlchemy", - "sSkillAlteration", - "sSkillArmorer", - "sSkillAthletics", - "sSkillAxe", - "sSkillBlock", - "sSkillBluntweapon", - "sSkillClassMajor", - "sSkillClassMinor", - "sSkillClassMisc", - "sSkillConjuration", - "sSkillDestruction", - "sSkillEnchant", - "sSkillHandtohand", - "sSkillHeavyarmor", - "sSkillIllusion", - "sSkillLightarmor", - "sSkillLongblade", - "sSkillMarksman", - "sSkillMaxReached", - "sSkillMediumarmor", - "sSkillMercantile", - "sSkillMysticism", - "sSkillProgress", - "sSkillRestoration", - "sSkillSecurity", - "sSkillShortblade", - "sSkillsMenu1", - "sSkillsMenuReputationHelp", - "sSkillSneak", - "sSkillSpear", - "sSkillSpeechcraft", - "sSkillUnarmored", - "sSlash", - "sSleepInterrupt", - "sSlideLeftXbox", - "sSlideRightXbox", - "sSlow", - "sSorceror", - "sSoulGem", - "sSoulGemsWithSouls", - "sSoultrapSuccess", - "sSpace", - "sSpdDesc", - "sSpecialization", - "sSpecializationCombat", - "sSpecializationMagic", - "sSpecializationMenu1", - "sSpecializationStealth", - "sSpellmaking", - "sSpellmakingHelp1", - "sSpellmakingHelp2", - "sSpellmakingHelp3", - "sSpellmakingHelp4", - "sSpellmakingHelp5", - "sSpellmakingHelp6", - "sSpellmakingMenu1", - "sSpellmakingMenuTitle", - "sSpells", - "sSpellServiceTitle", - "sSpellsword", - "sStartCell", - "sStartCellError", - "sStartError", - "sStats", - "sStrafe", - "sStrDesc", - "sStrip", - "sSubtitles", - "sSystemMenuXbox", - "sTake", - "sTakeAll", - "sTargetCriticalStrike", - "sTaunt", - "sTauntFail", - "sTauntSuccess", - "sTeleportDisabled", - "sThief", - "sThrust", - "sTo", - "sTogglePOVCmd", - "sTogglePOVXbox", - "sToggleRunXbox", - "sTopics", - "sTotalCost", - "sTotalSold", - "sTraining", - "sTrainingServiceTitle", - "sTraits", - "sTransparency_Menu", - "sTrapFail", - "sTrapImpossible", - "sTrapped", - "sTrapSuccess", - "sTravel", - "sTravelServiceTitle", - "sTurn", - "sTurnLeftXbox", - "sTurnRightXbox", - "sTwoHanded", - "sType", - "sTypeAbility", - "sTypeBlightDisease", - "sTypeCurse", - "sTypeDisease", - "sTypePower", - "sTypeSpell", - "sUnequip", - "sUnlocked", - "sUntilHealed", - "sUse", - "sUserDefinedClass", - "sUses", - "sUseXbox", - "sValue", - "sVideo", - "sVideoWarning", - "sVoice", - "sWait", - "sWarrior", - "sWaterReflectUpdate", - "sWaterTerrainReflect", - "sWeaponTab", - "sWeight", - "sWerewolfAlarmMessage", - "sWerewolfPopup", - "sWerewolfRefusal", - "sWerewolfRestMessage", - "sWilDesc", - "sWitchhunter", - "sWorld", - "sWornTab", - "sXStrafe", - "sXTimes", - "sXTimesINT", - "sYes", - "sYourGold", - 0 - }; - - for (int i=0; gmstFloats[i]; i++) + for (size_t i=0; i < CSMWorld::DefaultGmsts::FloatCount; ++i) { ESM::GameSetting gmst; - gmst.mId = gmstFloats[i]; + gmst.mId = CSMWorld::DefaultGmsts::Floats[i]; gmst.mValue.setType (ESM::VT_Float); - gmst.mValue.setFloat (gmstFloatsValues[i]); + gmst.mValue.setFloat (CSMWorld::DefaultGmsts::FloatsDefaultValues[i]); getData().getGmsts().add (gmst); } - for (int i=0; gmstIntegers[i]; i++) + for (size_t i=0; i < CSMWorld::DefaultGmsts::IntCount; ++i) { ESM::GameSetting gmst; - gmst.mId = gmstIntegers[i]; + gmst.mId = CSMWorld::DefaultGmsts::Ints[i]; gmst.mValue.setType (ESM::VT_Int); - gmst.mValue.setInteger (gmstIntegersValues[i]); + gmst.mValue.setInteger (CSMWorld::DefaultGmsts::IntsDefaultValues[i]); getData().getGmsts().add (gmst); } - for (int i=0; gmstStrings[i]; i++) + for (size_t i=0; i < CSMWorld::DefaultGmsts::StringCount; ++i) { ESM::GameSetting gmst; - gmst.mId = gmstStrings[i]; + gmst.mId = CSMWorld::DefaultGmsts::Strings[i]; gmst.mValue.setType (ESM::VT_String); gmst.mValue.setString (""); getData().getGmsts().add (gmst); @@ -1933,115 +44,28 @@ void CSMDoc::Document::addGmsts() void CSMDoc::Document::addOptionalGmsts() { - static const char *sFloats[] = - { - "fCombatDistanceWerewolfMod", - "fFleeDistance", - "fWereWolfAcrobatics", - "fWereWolfAgility", - "fWereWolfAlchemy", - "fWereWolfAlteration", - "fWereWolfArmorer", - "fWereWolfAthletics", - "fWereWolfAxe", - "fWereWolfBlock", - "fWereWolfBluntWeapon", - "fWereWolfConjuration", - "fWereWolfDestruction", - "fWereWolfEnchant", - "fWereWolfEndurance", - "fWereWolfFatigue", - "fWereWolfHandtoHand", - "fWereWolfHealth", - "fWereWolfHeavyArmor", - "fWereWolfIllusion", - "fWereWolfIntellegence", - "fWereWolfLightArmor", - "fWereWolfLongBlade", - "fWereWolfLuck", - "fWereWolfMagicka", - "fWereWolfMarksman", - "fWereWolfMediumArmor", - "fWereWolfMerchantile", - "fWereWolfMysticism", - "fWereWolfPersonality", - "fWereWolfRestoration", - "fWereWolfRunMult", - "fWereWolfSecurity", - "fWereWolfShortBlade", - "fWereWolfSilverWeaponDamageMult", - "fWereWolfSneak", - "fWereWolfSpear", - "fWereWolfSpeechcraft", - "fWereWolfSpeed", - "fWereWolfStrength", - "fWereWolfUnarmored", - "fWereWolfWillPower", - 0 - }; - - static const char *sIntegers[] = - { - "iWereWolfBounty", - "iWereWolfFightMod", - "iWereWolfFleeMod", - "iWereWolfLevelToAttack", - 0 - }; - - static const char *sStrings[] = - { - "sCompanionShare", - "sCompanionWarningButtonOne", - "sCompanionWarningButtonTwo", - "sCompanionWarningMessage", - "sDeleteNote", - "sEditNote", - "sEffectSummonCreature01", - "sEffectSummonCreature02", - "sEffectSummonCreature03", - "sEffectSummonCreature04", - "sEffectSummonCreature05", - "sEffectSummonFabricant", - "sLevitateDisabled", - "sMagicCreature01ID", - "sMagicCreature02ID", - "sMagicCreature03ID", - "sMagicCreature04ID", - "sMagicCreature05ID", - "sMagicFabricantID", - "sMaxSale", - "sProfitValue", - "sTeleportDisabled", - "sWerewolfAlarmMessage", - "sWerewolfPopup", - "sWerewolfRefusal", - "sWerewolfRestMessage", - 0 - }; - - for (int i=0; sFloats[i]; ++i) + for (size_t i=0; i < CSMWorld::DefaultGmsts::OptionalFloatCount; ++i) { ESM::GameSetting gmst; - gmst.mId = sFloats[i]; + gmst.mId = CSMWorld::DefaultGmsts::OptionalFloats[i]; gmst.blank(); gmst.mValue.setType (ESM::VT_Float); addOptionalGmst (gmst); } - for (int i=0; sIntegers[i]; ++i) + for (size_t i=0; i < CSMWorld::DefaultGmsts::OptionalIntCount; ++i) { ESM::GameSetting gmst; - gmst.mId = sIntegers[i]; + gmst.mId = CSMWorld::DefaultGmsts::OptionalInts[i]; gmst.blank(); gmst.mValue.setType (ESM::VT_Int); addOptionalGmst (gmst); } - for (int i=0; sStrings[i]; ++i) + for (size_t i=0; i < CSMWorld::DefaultGmsts::OptionalStringCount; ++i) { ESM::GameSetting gmst; - gmst.mId = sStrings[i]; + gmst.mId = CSMWorld::DefaultGmsts::OptionalStrings[i]; gmst.blank(); gmst.mValue.setType (ESM::VT_String); gmst.mValue.setString (""); @@ -2247,15 +271,16 @@ void CSMDoc::Document::createBase() CSMDoc::Document::Document (const VFS::Manager* vfs, const Files::ConfigurationManager& configuration, const std::vector< boost::filesystem::path >& files, bool new_, const boost::filesystem::path& savePath, const boost::filesystem::path& resDir, + const Fallback::Map* fallback, ToUTF8::FromType encoding, const CSMWorld::ResourcesManager& resourcesManager, const std::vector& blacklistedScripts) -: mVFS(vfs), mSavePath (savePath), mContentFiles (files), mNew (new_), mData (encoding, resourcesManager), +: mVFS(vfs), mSavePath (savePath), mContentFiles (files), mNew (new_), mData (encoding, resourcesManager, fallback, resDir), mTools (*this, encoding), mProjectPath ((configuration.getUserDataPath() / "projects") / (savePath.filename().string() + ".project")), mSavingOperation (*this, mProjectPath, encoding), mSaving (&mSavingOperation), - mResDir(resDir), + mResDir(resDir), mFallbackMap(fallback), mRunner (mProjectPath), mDirty (false), mIdCompletionManager(mData) { if (mContentFiles.empty()) diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp index 0e8ae6d454..41640f66d5 100644 --- a/apps/opencs/model/doc/document.hpp +++ b/apps/opencs/model/doc/document.hpp @@ -25,9 +25,13 @@ class QAbstractItemModel; +namespace Fallback +{ + class Map; +} + namespace VFS { - class Manager; } @@ -66,6 +70,7 @@ namespace CSMDoc Saving mSavingOperation; OperationHolder mSaving; boost::filesystem::path mResDir; + const Fallback::Map* mFallbackMap; Blacklist mBlacklist; Runner mRunner; bool mDirty; @@ -101,6 +106,7 @@ namespace CSMDoc Document (const VFS::Manager* vfs, const Files::ConfigurationManager& configuration, const std::vector< boost::filesystem::path >& files, bool new_, const boost::filesystem::path& savePath, const boost::filesystem::path& resDir, + const Fallback::Map* fallback, ToUTF8::FromType encoding, const CSMWorld::ResourcesManager& resourcesManager, const std::vector& blacklistedScripts); diff --git a/apps/opencs/model/doc/documentmanager.cpp b/apps/opencs/model/doc/documentmanager.cpp index 407a608cab..87fb960c1a 100644 --- a/apps/opencs/model/doc/documentmanager.cpp +++ b/apps/opencs/model/doc/documentmanager.cpp @@ -41,6 +41,7 @@ CSMDoc::DocumentManager::DocumentManager (const Files::ConfigurationManager& con CSMDoc::DocumentManager::~DocumentManager() { mLoaderThread.quit(); + mLoader.stop(); mLoader.hasThingsToDo().wakeAll(); mLoaderThread.wait(); @@ -64,7 +65,7 @@ CSMDoc::Document *CSMDoc::DocumentManager::makeDocument ( const std::vector< boost::filesystem::path >& files, const boost::filesystem::path& savePath, bool new_) { - return new Document (mVFS, mConfiguration, files, new_, savePath, mResDir, mEncoding, mResourcesManager, mBlacklistedScripts); + return new Document (mVFS, mConfiguration, files, new_, savePath, mResDir, &mFallbackMap, mEncoding, mResourcesManager, mBlacklistedScripts); } void CSMDoc::DocumentManager::insertDocument (CSMDoc::Document *document) @@ -100,6 +101,11 @@ void CSMDoc::DocumentManager::setResourceDir (const boost::filesystem::path& par mResDir = boost::filesystem::system_complete(parResDir); } +void CSMDoc::DocumentManager::setFallbackMap(const std::map& fallbackMap) +{ + mFallbackMap = Fallback::Map(fallbackMap); +} + void CSMDoc::DocumentManager::setEncoding (ToUTF8::FromType encoding) { mEncoding = encoding; diff --git a/apps/opencs/model/doc/documentmanager.hpp b/apps/opencs/model/doc/documentmanager.hpp index 4f6b2b2c99..ed8e327d7b 100644 --- a/apps/opencs/model/doc/documentmanager.hpp +++ b/apps/opencs/model/doc/documentmanager.hpp @@ -10,6 +10,7 @@ #include #include +#include #include "../world/resourcesmanager.hpp" @@ -67,6 +68,8 @@ namespace CSMDoc void setResourceDir (const boost::filesystem::path& parResDir); + void setFallbackMap (const std::map& fallbackMap); + void setEncoding (ToUTF8::FromType encoding); void setBlacklistedScripts (const std::vector& scriptIds); @@ -78,6 +81,7 @@ namespace CSMDoc private: boost::filesystem::path mResDir; + Fallback::Map mFallbackMap; private slots: diff --git a/apps/opencs/model/doc/loader.cpp b/apps/opencs/model/doc/loader.cpp index cb3ff2cd0f..51b1b5884e 100644 --- a/apps/opencs/model/doc/loader.cpp +++ b/apps/opencs/model/doc/loader.cpp @@ -1,6 +1,6 @@ #include "loader.hpp" -#include +#include #include "../tools/reportmodel.hpp" @@ -11,11 +11,12 @@ CSMDoc::Loader::Stage::Stage() : mFile (0), mRecordsLoaded (0), mRecordsLeft (fa CSMDoc::Loader::Loader() + : mShouldStop(false) { - QTimer *timer = new QTimer (this); + mTimer = new QTimer (this); - connect (timer, SIGNAL (timeout()), this, SLOT (load())); - timer->start(); + connect (mTimer, SIGNAL (timeout()), this, SLOT (load())); + mTimer->start(); } QWaitCondition& CSMDoc::Loader::hasThingsToDo() @@ -23,6 +24,11 @@ QWaitCondition& CSMDoc::Loader::hasThingsToDo() return mThingsToDo; } +void CSMDoc::Loader::stop() +{ + mShouldStop = true; +} + void CSMDoc::Loader::load() { if (mDocuments.empty()) @@ -30,6 +36,10 @@ void CSMDoc::Loader::load() mMutex.lock(); mThingsToDo.wait (&mMutex); mMutex.unlock(); + + if (mShouldStop) + mTimer->stop(); + return; } @@ -45,13 +55,12 @@ void CSMDoc::Loader::load() bool done = false; - const int batchingSize = 50; - try { if (iter->second.mRecordsLeft) { Messages messages (Message::Severity_Error); + const int batchingSize = 50; for (int i=0; igetData().continueLoading (messages)) { diff --git a/apps/opencs/model/doc/loader.hpp b/apps/opencs/model/doc/loader.hpp index d1ee38f9f1..ce5bc5848a 100644 --- a/apps/opencs/model/doc/loader.hpp +++ b/apps/opencs/model/doc/loader.hpp @@ -5,6 +5,7 @@ #include #include +#include #include namespace CSMDoc @@ -28,12 +29,17 @@ namespace CSMDoc QWaitCondition mThingsToDo; std::vector > mDocuments; + QTimer* mTimer; + bool mShouldStop; + public: Loader(); QWaitCondition& hasThingsToDo(); + void stop(); + private slots: void load(); diff --git a/apps/opencs/model/doc/messages.cpp b/apps/opencs/model/doc/messages.cpp index 86e96a88d6..76bbb6f227 100644 --- a/apps/opencs/model/doc/messages.cpp +++ b/apps/opencs/model/doc/messages.cpp @@ -1,6 +1,6 @@ #include "messages.hpp" -CSMDoc::Message::Message() {} +CSMDoc::Message::Message() : mSeverity(Severity_Default){} CSMDoc::Message::Message (const CSMWorld::UniversalId& id, const std::string& message, const std::string& hint, Severity severity) diff --git a/apps/opencs/model/doc/messages.hpp b/apps/opencs/model/doc/messages.hpp index 429feae4ec..4041e1a678 100644 --- a/apps/opencs/model/doc/messages.hpp +++ b/apps/opencs/model/doc/messages.hpp @@ -39,9 +39,6 @@ namespace CSMDoc { public: - // \deprecated Use CSMDoc::Message directly instead. - typedef CSMDoc::Message Message; - typedef std::vector Collection; typedef Collection::const_iterator Iterator; diff --git a/apps/opencs/model/doc/operationholder.cpp b/apps/opencs/model/doc/operationholder.cpp index 5fcf24fe4f..ccbed6c8ba 100644 --- a/apps/opencs/model/doc/operationholder.cpp +++ b/apps/opencs/model/doc/operationholder.cpp @@ -2,7 +2,9 @@ #include "operation.hpp" -CSMDoc::OperationHolder::OperationHolder (Operation *operation) : mRunning (false) +CSMDoc::OperationHolder::OperationHolder (Operation *operation) + : mOperation(NULL) + , mRunning (false) { if (operation) setOperation (operation); diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index db38c4779b..ee65248e2f 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -11,6 +11,7 @@ #include #include "../world/infocollection.hpp" +#include "../world/cellcoordinates.hpp" #include "document.hpp" #include "savingstate.hpp" @@ -238,7 +239,7 @@ void CSMDoc::CollectionReferencesStage::perform (int stage, Messages& messages) // An empty mOriginalCell is meant to indicate that it is the same as // the current cell. It is possible that a moved ref is moved again. if ((record.get().mOriginalCell.empty() ? - record.get().mCell : record.get().mOriginalCell) != stream.str() && !interior) + record.get().mCell : record.get().mOriginalCell) != stream.str() && !interior && record.mState!=CSMWorld::RecordBase::State_ModifiedOnly && !record.get().mNew) indices.push_back (i); else indices.push_front (i); @@ -265,13 +266,32 @@ void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages) std::map >::const_iterator references = mState.getSubRecords().find (Misc::StringUtils::lowerCase (cell.get().mId)); - if (cell.isModified() || + if (cell.isModified() || cell.mState == CSMWorld::RecordBase::State_Deleted || references!=mState.getSubRecords().end()) { CSMWorld::Cell cellRecord = cell.get(); bool interior = cellRecord.mId.substr (0, 1)!="#"; + // count new references and adjust RefNumCount accordingsly + int newRefNum = cellRecord.mRefNumCounter; + + if (references!=mState.getSubRecords().end()) + { + for (std::deque::const_iterator iter (references->second.begin()); + iter!=references->second.end(); ++iter) + { + const CSMWorld::Record& ref = + mDocument.getData().getReferences().getRecord (*iter); + + if (ref.get().mNew || + (!interior && ref.mState==CSMWorld::RecordBase::State_ModifiedOnly && + /// \todo consider worldspace + CSMWorld::CellCoordinates (ref.get().getCellIndex()).getId("")!=ref.get().mCell)) + ++cellRecord.mRefNumCounter; + } + } + // write cell data writer.startRecord (cellRecord.sRecordId); @@ -309,11 +329,18 @@ void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages) stream << "#" << index.first << " " << index.second; } - // An empty mOriginalCell is meant to indicate that it is the same as - // the current cell. It is possible that a moved ref is moved again. - if ((refRecord.mOriginalCell.empty() ? refRecord.mCell : refRecord.mOriginalCell) + if (refRecord.mNew || + (!interior && ref.mState==CSMWorld::RecordBase::State_ModifiedOnly && + refRecord.mCell!=stream.str())) + { + refRecord.mRefNum.mIndex = newRefNum++; + } + else if ((refRecord.mOriginalCell.empty() ? refRecord.mCell : refRecord.mOriginalCell) != stream.str() && !interior) { + // An empty mOriginalCell is meant to indicate that it is the same as + // the current cell. It is possible that a moved ref is moved again. + ESM::MovedCellRef moved; moved.mRefNum = refRecord.mRefNum; @@ -350,7 +377,7 @@ int CSMDoc::WritePathgridCollectionStage::setup() void CSMDoc::WritePathgridCollectionStage::perform (int stage, Messages& messages) { ESM::ESMWriter& writer = mState.getWriter(); - const CSMWorld::Record& pathgrid = + const CSMWorld::Record& pathgrid = mDocument.getData().getPathgrids().getRecord (stage); if (pathgrid.isModified() || pathgrid.mState == CSMWorld::RecordBase::State_Deleted) @@ -386,7 +413,7 @@ int CSMDoc::WriteLandCollectionStage::setup() void CSMDoc::WriteLandCollectionStage::perform (int stage, Messages& messages) { ESM::ESMWriter& writer = mState.getWriter(); - const CSMWorld::Record& land = + const CSMWorld::Record& land = mDocument.getData().getLand().getRecord (stage); if (land.isModified() || land.mState == CSMWorld::RecordBase::State_Deleted) @@ -394,10 +421,6 @@ void CSMDoc::WriteLandCollectionStage::perform (int stage, Messages& messages) CSMWorld::Land record = land.get(); writer.startRecord (record.sRecordId); record.save (writer, land.mState == CSMWorld::RecordBase::State_Deleted); - - if (const ESM::Land::LandData *data = record.getLandData (record.mDataTypes)) - data->save (mState.getWriter()); - writer.endRecord (record.sRecordId); } } @@ -416,7 +439,7 @@ int CSMDoc::WriteLandTextureCollectionStage::setup() void CSMDoc::WriteLandTextureCollectionStage::perform (int stage, Messages& messages) { ESM::ESMWriter& writer = mState.getWriter(); - const CSMWorld::Record& landTexture = + const CSMWorld::Record& landTexture = mDocument.getData().getLandTextures().getRecord (stage); if (landTexture.isModified() || landTexture.mState == CSMWorld::RecordBase::State_Deleted) diff --git a/apps/opencs/model/prefs/doublesetting.cpp b/apps/opencs/model/prefs/doublesetting.cpp index 7c247777d2..531196174a 100644 --- a/apps/opencs/model/prefs/doublesetting.cpp +++ b/apps/opencs/model/prefs/doublesetting.cpp @@ -15,10 +15,16 @@ CSMPrefs::DoubleSetting::DoubleSetting (Category *parent, Settings::Manager *values, QMutex *mutex, const std::string& key, const std::string& label, double default_) : Setting (parent, values, mutex, key, label), - mMin (0), mMax (std::numeric_limits::max()), + mPrecision(2), mMin (0), mMax (std::numeric_limits::max()), mDefault (default_) {} +CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setPrecision(int precision) +{ + mPrecision = precision; + return *this; +} + CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setRange (double min, double max) { mMin = min; @@ -49,6 +55,7 @@ std::pair CSMPrefs::DoubleSetting::makeWidgets (QWidget *p QLabel *label = new QLabel (QString::fromUtf8 (getLabel().c_str()), parent); QDoubleSpinBox *widget = new QDoubleSpinBox (parent); + widget->setDecimals(mPrecision); widget->setRange (mMin, mMax); widget->setValue (mDefault); diff --git a/apps/opencs/model/prefs/doublesetting.hpp b/apps/opencs/model/prefs/doublesetting.hpp index 3868f014e2..8fd345f4d0 100644 --- a/apps/opencs/model/prefs/doublesetting.hpp +++ b/apps/opencs/model/prefs/doublesetting.hpp @@ -9,6 +9,7 @@ namespace CSMPrefs { Q_OBJECT + int mPrecision; double mMin; double mMax; std::string mTooltip; @@ -20,6 +21,8 @@ namespace CSMPrefs QMutex *mutex, const std::string& key, const std::string& label, double default_); + DoubleSetting& setPrecision (int precision); + // defaults to [0, std::numeric_limits::max()] DoubleSetting& setRange (double min, double max); diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 8b827d0a2b..c70e71deb0 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -133,12 +133,17 @@ void CSMPrefs::State::declare() declareBool ("show-linenum", "Show Line Numbers", true). setTooltip ("Show line numbers to the left of the script editor window." "The current row and column numbers of the text cursor are shown at the bottom."); + declareBool ("wrap-lines", "Wrap Lines", false). + setTooltip ("Wrap lines longer than width of script editor."); declareBool ("mono-font", "Use monospace font", true); + declareInt ("tab-width", "Tab Width", 4). + setTooltip ("Number of characters for tab width"). + setRange (1, 10); EnumValue warningsNormal ("Normal", "Report warnings as warning"); declareEnum ("warnings", "Warning Mode", warningsNormal). addValue ("Ignore", "Do not report warning"). addValue (warningsNormal). - addValue ("Strcit", "Promote warning to an error"); + addValue ("Strict", "Promote warning to an error"); declareBool ("toolbar", "Show toolbar", true); declareInt ("compile-delay", "Delay between updating of source errors", 100). setTooltip ("Delay in milliseconds"). @@ -170,6 +175,17 @@ void CSMPrefs::State::declare() inputButtons.add (left).add (cLeft).add (right).add (cRight).add (middle).add (cMiddle); declareEnum ("p-navi", "Primary Camera Navigation Button", left).addValues (inputButtons); declareEnum ("s-navi", "Secondary Camera Navigation Button", cLeft).addValues (inputButtons); + declareDouble ("p-navi-free-sensitivity", "Free Camera Sensitivity", 1/650.).setPrecision(5).setRange(0.0, 1.0); + declareBool ("p-navi-free-invert", "Invert Free Camera Mouse Input", false); + declareDouble ("p-navi-orbit-sensitivity", "Orbit Camera Sensitivity", 1/650.).setPrecision(5).setRange(0.0, 1.0); + declareBool ("p-navi-orbit-invert", "Invert Orbit Camera Mouse Input", false); + declareDouble ("s-navi-sensitivity", "Secondary Camera Movement Sensitivity", 50.0).setRange(-1000.0, 1000.0); + declareDouble ("navi-wheel-factor", "Camera Zoom Sensitivity", 8).setRange(-100.0, 100.0); + declareDouble ("navi-free-lin-speed", "Free Camera Linear Speed", 1000.0).setRange(1.0, 10000.0); + declareDouble ("navi-free-rot-speed", "Free Camera Rotational Speed", 3.14 / 2).setRange(0.001, 6.28); + declareDouble ("navi-free-speed-mult", "Free Camera Speed Multiplier (from Modifier)", 8).setRange(0.001, 1000.0); + declareDouble ("navi-orbit-rot-speed", "Orbital Camera Rotational Speed", 3.14 / 4).setRange(0.001, 6.28); + declareDouble ("navi-orbit-speed-mult", "Orbital Camera Speed Multiplier (from Modifier)", 4).setRange(0.001, 1000.0); declareEnum ("p-edit", "Primary Editing Button", right).addValues (inputButtons); declareEnum ("s-edit", "Secondary Editing Button", cRight).addValues (inputButtons); declareEnum ("p-select", "Primary Selection Button", middle).addValues (inputButtons); @@ -190,6 +206,24 @@ void CSMPrefs::State::declare() declareBool ("scene-hide-basic", "Hide basic 3D scenes tooltips", false); declareInt ("scene-delay", "Tooltip delay in milliseconds", 500). setMin (1); + + EnumValue createAndInsert ("Create cell and insert"); + EnumValue showAndInsert ("Show cell and insert"); + EnumValue dontInsert ("Discard"); + EnumValue insertAnyway ("Insert anyway"); + EnumValues insertOutsideCell; + insertOutsideCell.add (createAndInsert).add (dontInsert).add (insertAnyway); + EnumValues insertOutsideVisibleCell; + insertOutsideVisibleCell.add (showAndInsert).add (dontInsert).add (insertAnyway); + + declareCategory ("Scene Drops"); + declareInt ("distance", "Drop Distance", 50). + setTooltip ("If an instance drop can not be placed against another object at the " + "insert point, it will be placed by this distance from the insert point instead"); + declareEnum ("outside-drop", "Handling drops outside of cells", createAndInsert). + addValues (insertOutsideCell); + declareEnum ("outside-visible-drop", "Handling drops outside of visible cells", showAndInsert). + addValues (insertOutsideVisibleCell); } void CSMPrefs::State::declareCategory (const std::string& key) diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index bcd76c6712..fffadee5e6 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -1,4 +1,4 @@ -#ifndef CSV_PREFS_STATE_H +#ifndef CSM_PREFS_STATE_H #define CSM_PREFS_STATE_H #include diff --git a/apps/opencs/model/tools/gmstcheck.cpp b/apps/opencs/model/tools/gmstcheck.cpp new file mode 100644 index 0000000000..0c32c00564 --- /dev/null +++ b/apps/opencs/model/tools/gmstcheck.cpp @@ -0,0 +1,123 @@ +#include "gmstcheck.hpp" + +#include + +#include "../world/defaultgmsts.hpp" + +CSMTools::GmstCheckStage::GmstCheckStage(const CSMWorld::IdCollection& gameSettings) + : mGameSettings(gameSettings) +{} + +int CSMTools::GmstCheckStage::setup() +{ + return mGameSettings.getSize(); +} + +void CSMTools::GmstCheckStage::perform(int stage, CSMDoc::Messages& messages) +{ + const CSMWorld::Record& record = mGameSettings.getRecord (stage); + + if (record.isDeleted()) + return; + + const ESM::GameSetting& gmst = record.get(); + + CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Gmst, gmst.mId); + + // Test for empty string + if (gmst.mValue.getType() == ESM::VT_String && gmst.mValue.getString().empty()) + messages.add(id, gmst.mId + " is an empty string", "", CSMDoc::Message::Severity_Warning); + + // Checking type and limits + // optimization - compare it to lists based on naming convention (f-float,i-int,s-string) + if (gmst.mId[0] == 'f') + { + for (size_t i = 0; i < CSMWorld::DefaultGmsts::FloatCount; ++i) + { + if (gmst.mId == CSMWorld::DefaultGmsts::Floats[i]) + { + if (gmst.mValue.getType() != ESM::VT_Float) + { + std::ostringstream stream; + stream << "Expected float type for " << gmst.mId << " but found " + << varTypeToString(gmst.mValue.getType()) << " type"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); + } + + if (gmst.mValue.getFloat() < CSMWorld::DefaultGmsts::FloatLimits[i*2]) + messages.add(id, gmst.mId + " is less than the suggested range", "", + CSMDoc::Message::Severity_Warning); + + if (gmst.mValue.getFloat() > CSMWorld::DefaultGmsts::FloatLimits[i*2+1]) + messages.add(id, gmst.mId + " is more than the suggested range", "", + CSMDoc::Message::Severity_Warning); + + break; // for loop + } + } + } + else if (gmst.mId[0] == 'i') + { + for (size_t i = 0; i < CSMWorld::DefaultGmsts::IntCount; ++i) + { + if (gmst.mId == CSMWorld::DefaultGmsts::Ints[i]) + { + if (gmst.mValue.getType() != ESM::VT_Int) + { + std::ostringstream stream; + stream << "Expected int type for " << gmst.mId << " but found " + << varTypeToString(gmst.mValue.getType()) << " type"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); + } + + if (gmst.mValue.getInteger() < CSMWorld::DefaultGmsts::IntLimits[i*2]) + messages.add(id, gmst.mId + " is less than the suggested range", "", + CSMDoc::Message::Severity_Warning); + + if (gmst.mValue.getInteger() > CSMWorld::DefaultGmsts::IntLimits[i*2+1]) + messages.add(id, gmst.mId + " is more than the suggested range", "", + CSMDoc::Message::Severity_Warning); + + break; // for loop + } + } + } + else if (gmst.mId[0] == 's') + { + for (size_t i = 0; i < CSMWorld::DefaultGmsts::StringCount; ++i) + { + if (gmst.mId == CSMWorld::DefaultGmsts::Strings[i]) + { + ESM::VarType type = gmst.mValue.getType(); + + if (type != ESM::VT_String && type != ESM::VT_None) + { + std::ostringstream stream; + stream << "Expected string or none type for " << gmst.mId << " but found " + << varTypeToString(gmst.mValue.getType()) << " type"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); + } + + break; // for loop + } + } + } +} + +std::string CSMTools::GmstCheckStage::varTypeToString(ESM::VarType type) +{ + switch (type) + { + case ESM::VT_Unknown: return "unknown"; + case ESM::VT_None: return "none"; + case ESM::VT_Short: return "short"; + case ESM::VT_Int: return "int"; + case ESM::VT_Long: return "long"; + case ESM::VT_Float: return "float"; + case ESM::VT_String: return "string"; + default: return "unhandled"; + } +} diff --git a/apps/opencs/model/tools/gmstcheck.hpp b/apps/opencs/model/tools/gmstcheck.hpp new file mode 100644 index 0000000000..0d4f7f2046 --- /dev/null +++ b/apps/opencs/model/tools/gmstcheck.hpp @@ -0,0 +1,34 @@ +#ifndef CSM_TOOLS_GMSTCHECK_H +#define CSM_TOOLS_GMSTCHECK_H + +#include + +#include "../world/idcollection.hpp" + +#include "../doc/stage.hpp" + +namespace CSMTools +{ + /// \brief VerifyStage: make sure that GMSTs are alright + class GmstCheckStage : public CSMDoc::Stage + { + public: + + GmstCheckStage(const CSMWorld::IdCollection& gameSettings); + + virtual int setup(); + ///< \return number of steps + + virtual void perform(int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages + + private: + + const CSMWorld::IdCollection& mGameSettings; + + std::string varTypeToString(ESM::VarType); + + }; +} + +#endif diff --git a/apps/opencs/model/tools/journalcheck.cpp b/apps/opencs/model/tools/journalcheck.cpp new file mode 100644 index 0000000000..bdd14ddf0d --- /dev/null +++ b/apps/opencs/model/tools/journalcheck.cpp @@ -0,0 +1,79 @@ +#include "journalcheck.hpp" + +#include +#include + +CSMTools::JournalCheckStage::JournalCheckStage(const CSMWorld::IdCollection &journals, + const CSMWorld::InfoCollection& journalInfos) + : mJournals(journals), mJournalInfos(journalInfos) +{} + +int CSMTools::JournalCheckStage::setup() +{ + return mJournals.getSize(); +} + +void CSMTools::JournalCheckStage::perform(int stage, CSMDoc::Messages& messages) +{ + const CSMWorld::Record &journalRecord = mJournals.getRecord(stage); + + if (journalRecord.isDeleted()) + return; + + const ESM::Dialogue &journal = journalRecord.get(); + int statusNamedCount = 0; + int totalInfoCount = 0; + std::set questIndices; + + CSMWorld::InfoCollection::Range range = mJournalInfos.getTopicRange(journal.mId); + + for (CSMWorld::InfoCollection::RecordConstIterator it = range.first; it != range.second; ++it) + { + const CSMWorld::Record infoRecord = (*it); + + if (infoRecord.isDeleted()) + continue; + + const CSMWorld::Info& journalInfo = infoRecord.get(); + + totalInfoCount += 1; + + if (journalInfo.mQuestStatus == ESM::DialInfo::QS_Name) + { + statusNamedCount += 1; + } + + if (journalInfo.mResponse.empty()) + { + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_JournalInfo, journalInfo.mId); + + messages.add(id, "Journal Info: missing description", "", CSMDoc::Message::Severity_Warning); + } + + std::pair::iterator, bool> result = questIndices.insert(journalInfo.mData.mJournalIndex); + + // Duplicate index + if (result.second == false) + { + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_JournalInfo, journalInfo.mId); + + std::ostringstream stream; + stream << "Journal: duplicated quest index " << journalInfo.mData.mJournalIndex; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); + } + } + + if (totalInfoCount == 0) + { + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Journal, journal.mId); + + messages.add(id, "Journal: no defined Journal Infos", "", CSMDoc::Message::Severity_Warning); + } + else if (statusNamedCount > 1) + { + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Journal, journal.mId); + + messages.add(id, "Journal: multiple infos with quest status \"Named\"", "", CSMDoc::Message::Severity_Error); + } +} diff --git a/apps/opencs/model/tools/journalcheck.hpp b/apps/opencs/model/tools/journalcheck.hpp new file mode 100644 index 0000000000..c9f6196984 --- /dev/null +++ b/apps/opencs/model/tools/journalcheck.hpp @@ -0,0 +1,35 @@ +#ifndef CSM_TOOLS_JOURNALCHECK_H +#define CSM_TOOLS_JOURNALCHECK_H + +#include + +#include "../world/idcollection.hpp" +#include "../world/infocollection.hpp" + +#include "../doc/stage.hpp" + +namespace CSMTools +{ + /// \brief VerifyStage: make sure that journal infos are good + class JournalCheckStage : public CSMDoc::Stage + { + public: + + JournalCheckStage(const CSMWorld::IdCollection& journals, + const CSMWorld::InfoCollection& journalInfos); + + virtual int setup(); + ///< \return number of steps + + virtual void perform(int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages + + private: + + const CSMWorld::IdCollection& mJournals; + const CSMWorld::InfoCollection& mJournalInfos; + + }; +} + +#endif diff --git a/apps/opencs/model/tools/magiceffectcheck.cpp b/apps/opencs/model/tools/magiceffectcheck.cpp index 5435881b34..ab8b3b68bd 100644 --- a/apps/opencs/model/tools/magiceffectcheck.cpp +++ b/apps/opencs/model/tools/magiceffectcheck.cpp @@ -7,7 +7,7 @@ namespace { - void addMessageIfNotEmpty(CSMDoc::Messages &messages, const CSMWorld::UniversalId &id, const std::string text) + void addMessageIfNotEmpty(CSMDoc::Messages &messages, const CSMWorld::UniversalId &id, const std::string& text) { if (!text.empty()) { diff --git a/apps/opencs/model/tools/mergestages.cpp b/apps/opencs/model/tools/mergestages.cpp index 52e1e69649..4d4835ecea 100644 --- a/apps/opencs/model/tools/mergestages.cpp +++ b/apps/opencs/model/tools/mergestages.cpp @@ -99,6 +99,7 @@ void CSMTools::MergeReferencesStage::perform (int stage, CSMDoc::Messages& messa ref.mRefNum.mIndex = mIndex[Misc::StringUtils::lowerCase (ref.mCell)]++; ref.mRefNum.mContentFile = 0; + ref.mNew = false; CSMWorld::Record newRecord ( CSMWorld::RecordBase::State_ModifiedOnly, 0, &ref); @@ -224,8 +225,6 @@ void CSMTools::MergeLandStage::perform (int stage, CSMDoc::Messages& messages) CSMWorld::Land newLand (land); - newLand.mEsm = 0; // avoid potential dangling pointer (ESMReader isn't needed anyway, - // because record is already fully loaded) newLand.mPlugin = 0; if (land.mDataTypes & ESM::Land::DATA_VTEX) diff --git a/apps/opencs/model/tools/pathgridcheck.cpp b/apps/opencs/model/tools/pathgridcheck.cpp index 69ee5a8098..3cd4a1b09c 100644 --- a/apps/opencs/model/tools/pathgridcheck.cpp +++ b/apps/opencs/model/tools/pathgridcheck.cpp @@ -70,20 +70,6 @@ void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& message for (unsigned int i = 0; i < pathgrid.mPoints.size(); ++i) { - // check the connection number for each point matches the edge connections - if (pathgrid.mPoints[i].mConnectionNum > pointList[i].mConnectionNum) - { - std::ostringstream ss; - ss << " has has less edges than expected for point " << i; - messages.add (id, pathgrid.mId + ss.str(), "", CSMDoc::Message::Severity_Error); - } - else if (pathgrid.mPoints[i].mConnectionNum < pointList[i].mConnectionNum) - { - std::ostringstream ss; - ss << " has has more edges than expected for point " << i; - messages.add (id, pathgrid.mId + ss.str(), "", CSMDoc::Message::Severity_Error); - } - // check that edges are bidirectional bool foundReverse = false; for (unsigned int j = 0; j < pointList[i].mOtherIndex.size(); ++j) diff --git a/apps/opencs/model/tools/regioncheck.cpp b/apps/opencs/model/tools/regioncheck.cpp index 2fdff5f388..7348610804 100644 --- a/apps/opencs/model/tools/regioncheck.cpp +++ b/apps/opencs/model/tools/regioncheck.cpp @@ -29,9 +29,16 @@ void CSMTools::RegionCheckStage::perform (int stage, CSMDoc::Messages& messages) // test for empty name if (region.mName.empty()) - messages.push_back (std::make_pair (id, region.mId + " has an empty name")); + messages.add(id, region.mId + " has an empty name", "", CSMDoc::Message::Severity_Error); /// \todo test that the ID in mSleeplist exists + // test that chances add up to 100 + int chances = region.mData.mClear + region.mData.mCloudy + region.mData.mFoggy + region.mData.mOvercast + + region.mData.mRain + region.mData.mThunder + region.mData.mAsh + region.mData.mBlight + + region.mData.mA + region.mData.mB; + if (chances != 100) + messages.add(id, "Weather chances do not add up to 100", "", CSMDoc::Message::Severity_Error); + /// \todo check data members that can't be edited in the table view } diff --git a/apps/opencs/model/tools/reportmodel.cpp b/apps/opencs/model/tools/reportmodel.cpp index 77a14de841..49de1d6512 100644 --- a/apps/opencs/model/tools/reportmodel.cpp +++ b/apps/opencs/model/tools/reportmodel.cpp @@ -182,7 +182,7 @@ int CSMTools::ReportModel::countErrors() const { int count = 0; - for (std::vector::const_iterator iter (mRows.begin()); + for (std::vector::const_iterator iter (mRows.begin()); iter!=mRows.end(); ++iter) if (iter->mSeverity==CSMDoc::Message::Severity_Error || iter->mSeverity==CSMDoc::Message::Severity_SeriousError) diff --git a/apps/opencs/model/tools/reportmodel.hpp b/apps/opencs/model/tools/reportmodel.hpp index 5704970f55..61b4e63074 100644 --- a/apps/opencs/model/tools/reportmodel.hpp +++ b/apps/opencs/model/tools/reportmodel.hpp @@ -16,7 +16,7 @@ namespace CSMTools { Q_OBJECT - std::vector mRows; + std::vector mRows; // Fixed columns enum Columns diff --git a/apps/opencs/model/tools/search.cpp b/apps/opencs/model/tools/search.cpp index 0409199afe..0c068ba119 100644 --- a/apps/opencs/model/tools/search.cpp +++ b/apps/opencs/model/tools/search.cpp @@ -120,24 +120,25 @@ QString CSMTools::Search::flatten (const QString& text) const return flat; } -CSMTools::Search::Search() : mType (Type_None), mPaddingBefore (10), mPaddingAfter (10) {} +CSMTools::Search::Search() : mType (Type_None), mValue (0), mIdColumn (0), mTypeColumn (0), + mPaddingBefore (10), mPaddingAfter (10) {} CSMTools::Search::Search (Type type, const std::string& value) -: mType (type), mText (value), mPaddingBefore (10), mPaddingAfter (10) +: mType (type), mText (value), mValue (0), mIdColumn (0), mTypeColumn (0), mPaddingBefore (10), mPaddingAfter (10) { if (type!=Type_Text && type!=Type_Id) throw std::logic_error ("Invalid search parameter (string)"); } CSMTools::Search::Search (Type type, const QRegExp& value) -: mType (type), mRegExp (value), mPaddingBefore (10), mPaddingAfter (10) +: mType (type), mRegExp (value), mValue (0), mIdColumn (0), mTypeColumn (0), mPaddingBefore (10), mPaddingAfter (10) { if (type!=Type_TextRegEx && type!=Type_IdRegEx) throw std::logic_error ("Invalid search parameter (RegExp)"); } CSMTools::Search::Search (Type type, int value) -: mType (type), mValue (value), mPaddingBefore (10), mPaddingAfter (10) +: mType (type), mValue (value), mIdColumn (0), mTypeColumn (0), mPaddingBefore (10), mPaddingAfter (10) { if (type!=Type_RecordState) throw std::logic_error ("invalid search parameter (int)"); diff --git a/apps/opencs/model/tools/soundgencheck.cpp b/apps/opencs/model/tools/soundgencheck.cpp index bdf89f19d2..a36c494a1a 100644 --- a/apps/opencs/model/tools/soundgencheck.cpp +++ b/apps/opencs/model/tools/soundgencheck.cpp @@ -26,7 +26,7 @@ void CSMTools::SoundGenCheckStage::perform(int stage, CSMDoc::Messages &messages return; } - const ESM::SoundGenerator soundGen = record.get(); + const ESM::SoundGenerator& soundGen = record.get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_SoundGen, soundGen.mId); if (!soundGen.mCreature.empty()) diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index 608ed9922f..f538a716e6 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -29,6 +29,9 @@ #include "soundgencheck.hpp" #include "magiceffectcheck.hpp" #include "mergeoperation.hpp" +#include "gmstcheck.hpp" +#include "topicinfocheck.hpp" +#include "journalcheck.hpp" CSMDoc::OperationHolder *CSMTools::Tools::get (int type) { @@ -111,6 +114,23 @@ CSMDoc::OperationHolder *CSMTools::Tools::getVerifier() mData.getResources (CSMWorld::UniversalId::Type_Icons), mData.getResources (CSMWorld::UniversalId::Type_Textures))); + mVerifierOperation->appendStage (new GmstCheckStage (mData.getGmsts())); + + mVerifierOperation->appendStage (new TopicInfoCheckStage (mData.getTopicInfos(), + mData.getCells(), + mData.getClasses(), + mData.getFactions(), + mData.getGmsts(), + mData.getGlobals(), + mData.getJournals(), + mData.getRaces(), + mData.getRegions(), + mData.getTopics(), + mData.getReferenceables().getDataSet(), + mData.getResources (CSMWorld::UniversalId::Type_SoundsRes))); + + mVerifierOperation->appendStage (new JournalCheckStage(mData.getJournals(), mData.getJournalInfos())); + mVerifier.setOperation (mVerifierOperation); } diff --git a/apps/opencs/model/tools/topicinfocheck.cpp b/apps/opencs/model/tools/topicinfocheck.cpp new file mode 100644 index 0000000000..f6af2a9456 --- /dev/null +++ b/apps/opencs/model/tools/topicinfocheck.cpp @@ -0,0 +1,441 @@ +#include "topicinfocheck.hpp" + +#include + +#include "../world/infoselectwrapper.hpp" + +CSMTools::TopicInfoCheckStage::TopicInfoCheckStage( + const CSMWorld::InfoCollection& topicInfos, + const CSMWorld::IdCollection& cells, + const CSMWorld::IdCollection& classes, + const CSMWorld::IdCollection& factions, + const CSMWorld::IdCollection& gmsts, + const CSMWorld::IdCollection& globals, + const CSMWorld::IdCollection& journals, + const CSMWorld::IdCollection& races, + const CSMWorld::IdCollection& regions, + const CSMWorld::IdCollection &topics, + const CSMWorld::RefIdData& referencables, + const CSMWorld::Resources& soundFiles) + : mTopicInfos(topicInfos), + mCells(cells), + mClasses(classes), + mFactions(factions), + mGameSettings(gmsts), + mGlobals(globals), + mJournals(journals), + mRaces(races), + mRegions(regions), + mTopics(topics), + mReferencables(referencables), + mSoundFiles(soundFiles) +{} + +int CSMTools::TopicInfoCheckStage::setup() +{ + // Generate list of cell names for reference checking + + mCellNames.clear(); + for (int i = 0; i < mCells.getSize(); ++i) + { + const CSMWorld::Record& cellRecord = mCells.getRecord(i); + + if (cellRecord.isDeleted()) + continue; + + mCellNames.insert(cellRecord.get().mName); + } + // Cell names can also include region names + for (int i = 0; i < mRegions.getSize(); ++i) + { + const CSMWorld::Record& regionRecord = mRegions.getRecord(i); + + if (regionRecord.isDeleted()) + continue; + + mCellNames.insert(regionRecord.get().mName); + } + // Default cell name + int index = mGameSettings.searchId("sDefaultCellname"); + if (index != -1) + { + const CSMWorld::Record& gmstRecord = mGameSettings.getRecord(index); + + if (!gmstRecord.isDeleted() && gmstRecord.get().mValue.getType() == ESM::VT_String) + { + mCellNames.insert(gmstRecord.get().mValue.getString()); + } + } + + return mTopicInfos.getSize(); +} + +void CSMTools::TopicInfoCheckStage::perform(int stage, CSMDoc::Messages& messages) +{ + const CSMWorld::Record& infoRecord = mTopicInfos.getRecord(stage); + + if (infoRecord.isDeleted()) + return; + + const CSMWorld::Info& topicInfo = infoRecord.get(); + + // There should always be a topic that matches + int topicIndex = mTopics.searchId(topicInfo.mTopicId); + + const CSMWorld::Record& topicRecord = mTopics.getRecord(topicIndex); + + if (topicRecord.isDeleted()) + return; + + const ESM::Dialogue& topic = topicRecord.get(); + + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_TopicInfo, topicInfo.mId); + + // Check fields + + if (!topicInfo.mActor.empty()) + { + verifyActor(topicInfo.mActor, id, messages); + } + + if (!topicInfo.mClass.empty()) + { + verifyId(topicInfo.mClass, mClasses, id, messages); + } + + if (!topicInfo.mCell.empty()) + { + verifyCell(topicInfo.mCell, id, messages); + } + + if (!topicInfo.mFaction.empty()) + { + if (verifyId(topicInfo.mFaction, mFactions, id, messages)) + { + verifyFactionRank(topicInfo.mFaction, topicInfo.mData.mRank, id, messages); + } + } + + if (!topicInfo.mPcFaction.empty()) + { + if (verifyId(topicInfo.mPcFaction, mFactions, id, messages)) + { + verifyFactionRank(topicInfo.mPcFaction, topicInfo.mData.mPCrank, id, messages); + } + } + + if (topicInfo.mData.mGender < -1 || topicInfo.mData.mGender > 1) + { + std::ostringstream stream; + messages.add(id, "Gender: Value is invalid", "", CSMDoc::Message::Severity_Error); + } + + if (!topicInfo.mRace.empty()) + { + verifyId(topicInfo.mRace, mRaces, id, messages); + } + + if (!topicInfo.mSound.empty()) + { + verifySound(topicInfo.mSound, id, messages); + } + + if (topicInfo.mResponse.empty() && topic.mType != ESM::Dialogue::Voice) + { + messages.add(id, "Response is empty", "", CSMDoc::Message::Severity_Warning); + } + + // Check info conditions + + for (std::vector::const_iterator it = topicInfo.mSelects.begin(); + it != topicInfo.mSelects.end(); ++it) + { + verifySelectStruct((*it), id, messages); + } +} + +// Verification functions + +bool CSMTools::TopicInfoCheckStage::verifyActor(const std::string& actor, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages) +{ + const std::string specifier = "Actor"; + + CSMWorld::RefIdData::LocalIndex index = mReferencables.searchId(actor); + + if (index.first == -1) + { + writeMissingIdError(specifier, actor, id, messages); + return false; + } + else if (mReferencables.getRecord(index).isDeleted()) + { + writeDeletedRecordError(specifier, actor, id, messages); + return false; + } + else if (index.second != CSMWorld::UniversalId::Type_Npc && index.second != CSMWorld::UniversalId::Type_Creature) + { + writeInvalidTypeError(specifier, actor, index.second, "NPC or Creature", id, messages); + return false; + } + + return true; +} + +bool CSMTools::TopicInfoCheckStage::verifyCell(const std::string& cell, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages) +{ + const std::string specifier = "Cell"; + + if (mCellNames.find(cell) == mCellNames.end()) + { + writeMissingIdError(specifier, cell, id, messages); + return false; + } + + return true; +} + +bool CSMTools::TopicInfoCheckStage::verifyFactionRank(const std::string& factionName, int rank, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages) +{ + if (rank < -1) + { + std::ostringstream stream; + stream << "Rank or PC Rank is set to " << rank << ", but should be set to -1 if no rank is required"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); + return false; + } + + int index = mFactions.searchId(factionName); + + const ESM::Faction &faction = mFactions.getRecord(index).get(); + + int limit = 0; + for (; limit < 10; ++limit) + { + if (faction.mRanks[limit].empty()) + break; + } + + if (rank >= limit) + { + std::ostringstream stream; + stream << "Rank or PC Rank is set to " << rank << " which is more than the maximum of " << limit - 1 + << " for the " << factionName << " faction"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); + return false; + } + + return true; +} + +bool CSMTools::TopicInfoCheckStage::verifyItem(const std::string& item, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages) +{ + const std::string specifier = "Item"; + + CSMWorld::RefIdData::LocalIndex index = mReferencables.searchId(item); + + if (index.first == -1) + { + writeMissingIdError(specifier, item, id, messages); + return false; + } + else if (mReferencables.getRecord(index).isDeleted()) + { + writeDeletedRecordError(specifier, item, id, messages); + return false; + } + else + { + switch (index.second) + { + case CSMWorld::UniversalId::Type_Potion: + case CSMWorld::UniversalId::Type_Apparatus: + case CSMWorld::UniversalId::Type_Armor: + case CSMWorld::UniversalId::Type_Book: + case CSMWorld::UniversalId::Type_Clothing: + case CSMWorld::UniversalId::Type_Ingredient: + case CSMWorld::UniversalId::Type_Light: + case CSMWorld::UniversalId::Type_Lockpick: + case CSMWorld::UniversalId::Type_Miscellaneous: + case CSMWorld::UniversalId::Type_Probe: + case CSMWorld::UniversalId::Type_Repair: + case CSMWorld::UniversalId::Type_Weapon: + case CSMWorld::UniversalId::Type_ItemLevelledList: + break; + + default: + writeInvalidTypeError(specifier, item, index.second, "Potion, Armor, Book, etc.", id, messages); + return false; + } + } + + return true; +} + +bool CSMTools::TopicInfoCheckStage::verifySelectStruct(const ESM::DialInfo::SelectStruct& select, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) +{ + CSMWorld::ConstInfoSelectWrapper infoCondition(select); + + if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_None) + { + messages.add(id, "Invalid Info Condition: " + infoCondition.toString(), "", CSMDoc::Message::Severity_Error); + return false; + } + else if (!infoCondition.variantTypeIsValid()) + { + std::ostringstream stream; + stream << "Info Condition: Value for \"" << infoCondition.toString() << "\" has a type of "; + + switch (select.mValue.getType()) + { + case ESM::VT_None: stream << "None"; break; + case ESM::VT_Short: stream << "Short"; break; + case ESM::VT_Int: stream << "Int"; break; + case ESM::VT_Long: stream << "Long"; break; + case ESM::VT_Float: stream << "Float"; break; + case ESM::VT_String: stream << "String"; break; + default: stream << "Unknown"; break; + } + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); + return false; + } + else if (infoCondition.conditionIsAlwaysTrue()) + { + std::ostringstream stream; + stream << "Info Condition: " << infoCondition.toString() << " is always true"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Warning); + return false; + } + else if (infoCondition.conditionIsNeverTrue()) + { + std::ostringstream stream; + stream << "Info Condition: " << infoCondition.toString() << " is never true"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Warning); + return false; + } + + // Id checks + if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Global && + !verifyId(infoCondition.getVariableName(), mGlobals, id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Journal && + !verifyId(infoCondition.getVariableName(), mJournals, id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Item && + !verifyItem(infoCondition.getVariableName(), id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Dead && + !verifyActor(infoCondition.getVariableName(), id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotId && + !verifyActor(infoCondition.getVariableName(), id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotFaction && + !verifyId(infoCondition.getVariableName(), mFactions, id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotClass && + !verifyId(infoCondition.getVariableName(), mClasses, id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotRace && + !verifyId(infoCondition.getVariableName(), mRaces, id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotCell && + !verifyCell(infoCondition.getVariableName(), id, messages)) + { + return false; + } + + return true; +} + +bool CSMTools::TopicInfoCheckStage::verifySound(const std::string& sound, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages) +{ + const std::string specifier = "Sound File"; + + if (mSoundFiles.searchId(sound) == -1) + { + writeMissingIdError(specifier, sound, id, messages); + return false; + } + + return true; +} + +template +bool CSMTools::TopicInfoCheckStage::verifyId(const std::string& name, const CSMWorld::IdCollection& collection, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) +{ + int index = collection.searchId(name); + + if (index == -1) + { + writeMissingIdError(T::getRecordType(), name, id, messages); + return false; + } + else if (collection.getRecord(index).isDeleted()) + { + writeDeletedRecordError(T::getRecordType(), name, id, messages); + return false; + } + + return true; +} + +// Error functions + +void CSMTools::TopicInfoCheckStage::writeMissingIdError(const std::string& specifier, const std::string& missingId, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) +{ + std::ostringstream stream; + stream << specifier << ": ID or name \"" << missingId << "\" could not be found"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); +} + +void CSMTools::TopicInfoCheckStage::writeDeletedRecordError(const std::string& specifier, const std::string& recordId, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) +{ + std::ostringstream stream; + stream << specifier << ": Deleted record with ID \"" << recordId << "\" is being referenced"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); +} + +void CSMTools::TopicInfoCheckStage::writeInvalidTypeError(const std::string& specifier, const std::string& invalidId, + CSMWorld::UniversalId::Type invalidType, const std::string& expectedType, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages) +{ + CSMWorld::UniversalId tempId(invalidType, invalidId); + + std::ostringstream stream; + stream << specifier << ": invalid type of " << tempId.getTypeName() << " was found for referencable \"" + << invalidId << "\" (can be of type " << expectedType << ")"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); +} diff --git a/apps/opencs/model/tools/topicinfocheck.hpp b/apps/opencs/model/tools/topicinfocheck.hpp new file mode 100644 index 0000000000..510901dacc --- /dev/null +++ b/apps/opencs/model/tools/topicinfocheck.hpp @@ -0,0 +1,95 @@ +#ifndef CSM_TOOLS_TOPICINFOCHECK_HPP +#define CSM_TOOLS_TOPICINFOCHECK_HPP + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "../world/cell.hpp" +#include "../world/idcollection.hpp" +#include "../world/infocollection.hpp" +#include "../world/refiddata.hpp" +#include "../world/resources.hpp" + +#include "../doc/stage.hpp" + +namespace CSMTools +{ + /// \brief VerifyStage: check topics + class TopicInfoCheckStage : public CSMDoc::Stage + { + public: + + TopicInfoCheckStage( + const CSMWorld::InfoCollection& topicInfos, + const CSMWorld::IdCollection& cells, + const CSMWorld::IdCollection& classes, + const CSMWorld::IdCollection& factions, + const CSMWorld::IdCollection& gmsts, + const CSMWorld::IdCollection& globals, + const CSMWorld::IdCollection& journals, + const CSMWorld::IdCollection& races, + const CSMWorld::IdCollection& regions, + const CSMWorld::IdCollection& topics, + const CSMWorld::RefIdData& referencables, + const CSMWorld::Resources& soundFiles); + + virtual int setup(); + ///< \return number of steps + + virtual void perform(int step, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages + + private: + + const CSMWorld::InfoCollection& mTopicInfos; + + const CSMWorld::IdCollection& mCells; + const CSMWorld::IdCollection& mClasses; + const CSMWorld::IdCollection& mFactions; + const CSMWorld::IdCollection& mGameSettings; + const CSMWorld::IdCollection& mGlobals; + const CSMWorld::IdCollection& mJournals; + const CSMWorld::IdCollection& mRaces; + const CSMWorld::IdCollection& mRegions; + const CSMWorld::IdCollection& mTopics; + + const CSMWorld::RefIdData& mReferencables; + const CSMWorld::Resources& mSoundFiles; + + std::set mCellNames; + + // These return false when not successful and write an error + bool verifyActor(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + bool verifyCell(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + bool verifyFactionRank(const std::string& name, int rank, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages); + bool verifyItem(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + bool verifySelectStruct(const ESM::DialInfo::SelectStruct& select, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages); + bool verifySound(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + + template + bool verifyId(const std::string& name, const CSMWorld::IdCollection& collection, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + + // Common error messages + void writeMissingIdError(const std::string& specifier, const std::string& missingId, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + + void writeDeletedRecordError(const std::string& specifier, const std::string& recordId, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + + void writeInvalidTypeError(const std::string& specifier, const std::string& invalidId, + CSMWorld::UniversalId::Type invalidType, const std::string& expectedType, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + }; +} + +#endif diff --git a/apps/opencs/model/world/cellcoordinates.cpp b/apps/opencs/model/world/cellcoordinates.cpp index 3ef3e6c693..80ec9dc04d 100644 --- a/apps/opencs/model/world/cellcoordinates.cpp +++ b/apps/opencs/model/world/cellcoordinates.cpp @@ -1,5 +1,7 @@ #include "cellcoordinates.hpp" +#include + #include #include @@ -7,6 +9,9 @@ CSMWorld::CellCoordinates::CellCoordinates() : mX (0), mY (0) {} CSMWorld::CellCoordinates::CellCoordinates (int x, int y) : mX (x), mY (y) {} +CSMWorld::CellCoordinates::CellCoordinates (const std::pair& coordinates) +: mX (coordinates.first), mY (coordinates.second) {} + int CSMWorld::CellCoordinates::getX() const { return mX; @@ -49,6 +54,13 @@ std::pair CSMWorld::CellCoordinates::fromId ( return std::make_pair (CellCoordinates(), false); } +std::pair CSMWorld::CellCoordinates::coordinatesToCellIndex (float x, float y) +{ + const int cellSize = 8192; + + return std::make_pair (std::floor (x/cellSize), std::floor (y/cellSize)); +} + bool CSMWorld::operator== (const CellCoordinates& left, const CellCoordinates& right) { return left.getX()==right.getX() && left.getY()==right.getY(); diff --git a/apps/opencs/model/world/cellcoordinates.hpp b/apps/opencs/model/world/cellcoordinates.hpp index 63db60c29d..f8851a6d94 100644 --- a/apps/opencs/model/world/cellcoordinates.hpp +++ b/apps/opencs/model/world/cellcoordinates.hpp @@ -3,6 +3,7 @@ #include #include +#include #include @@ -19,6 +20,8 @@ namespace CSMWorld CellCoordinates (int x, int y); + CellCoordinates (const std::pair& coordinates); + int getX() const; int getY() const; @@ -34,6 +37,8 @@ namespace CSMWorld /// /// \note The worldspace part of \a id is ignored static std::pair fromId (const std::string& id); + + static std::pair coordinatesToCellIndex (float x, float y); }; bool operator== (const CellCoordinates& left, const CellCoordinates& right); diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index c75a3c2a12..1eb6a88c1c 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -85,6 +85,7 @@ namespace CSMWorld Display_Enchantment, //CONCRETE TYPES ENDS HERE + Display_UnsignedInteger8, Display_Integer, Display_Float, Display_Var, diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index d0d3a1671d..c116ec4e50 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -3,6 +3,7 @@ #include #include "universalid.hpp" +#include "infoselectwrapper.hpp" namespace CSMWorld { @@ -273,8 +274,8 @@ namespace CSMWorld { ColumnId_InfoList, "Info List" }, { ColumnId_InfoCondition, "Info Conditions" }, { ColumnId_InfoCondFunc, "Function" }, - { ColumnId_InfoCondVar, "Func/Variable" }, - { ColumnId_InfoCondComp, "Comp" }, + { ColumnId_InfoCondVar, "Variable/Object" }, + { ColumnId_InfoCondComp, "Relation" }, { ColumnId_InfoCondValue, "Values" }, { ColumnId_OriginalCell, "Original Cell" }, @@ -325,6 +326,10 @@ namespace CSMWorld { ColumnId_Idle7, "Idle 7" }, { ColumnId_Idle8, "Idle 8" }, + { ColumnId_RegionWeather, "Weather" }, + { ColumnId_WeatherName, "Type" }, + { ColumnId_WeatherChance, "Percent Chance" }, + { ColumnId_UseValue1, "Use value 1" }, { ColumnId_UseValue2, "Use value 2" }, { ColumnId_UseValue3, "Use value 3" }, @@ -546,18 +551,6 @@ namespace "AI Wander", "AI Travel", "AI Follow", "AI Escort", "AI Activate", 0 }; - static const char *sInfoCondFunc[] = - { - " ", "Function", "Global", "Local", "Journal", - "Item", "Dead", "Not ID", "Not Faction", "Not Class", - "Not Race", "Not Cell", "Not Local", 0 - }; - - static const char *sInfoCondComp[] = - { - "!=", "<", "<=", "=", ">", ">=", 0 - }; - const char **getEnumNames (CSMWorld::Columns::ColumnId column) { switch (column) @@ -585,10 +578,8 @@ namespace case CSMWorld::Columns::ColumnId_EffectId: return sEffectId; case CSMWorld::Columns::ColumnId_PartRefType: return sPartRefType; case CSMWorld::Columns::ColumnId_AiPackageType: return sAiPackageType; - case CSMWorld::Columns::ColumnId_InfoCondFunc: return sInfoCondFunc; - // FIXME: don't have dynamic value enum delegate, use Display_String for now - //case CSMWorld::Columns::ColumnId_InfoCond: return sInfoCond; - case CSMWorld::Columns::ColumnId_InfoCondComp: return sInfoCondComp; + case CSMWorld::Columns::ColumnId_InfoCondFunc: return CSMWorld::ConstInfoSelectWrapper::FunctionEnumStrings; + case CSMWorld::Columns::ColumnId_InfoCondComp: return CSMWorld::ConstInfoSelectWrapper::RelationEnumStrings; default: return 0; } diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index a504e5f659..05bedb77d5 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -325,6 +325,10 @@ namespace CSMWorld ColumnId_Idle7 = 292, ColumnId_Idle8 = 293, + ColumnId_RegionWeather = 294, + ColumnId_WeatherName = 295, + ColumnId_WeatherChance = 296, + // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. ColumnId_UseValue1 = 0x10000, diff --git a/apps/opencs/model/world/commanddispatcher.cpp b/apps/opencs/model/world/commanddispatcher.cpp index a1fc980eb5..ffbaa3decc 100644 --- a/apps/opencs/model/world/commanddispatcher.cpp +++ b/apps/opencs/model/world/commanddispatcher.cpp @@ -11,6 +11,7 @@ #include "record.hpp" #include "commands.hpp" #include "idtableproxymodel.hpp" +#include "commandmacro.hpp" std::vector CSMWorld::CommandDispatcher::getDeletableRecords() const { @@ -171,10 +172,9 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, cons if (modifyCell.get()) { - mDocument.getUndoStack().beginMacro (modifyData->text()); - mDocument.getUndoStack().push (modifyData.release()); - mDocument.getUndoStack().push (modifyCell.release()); - mDocument.getUndoStack().endMacro(); + CommandMacro macro (mDocument.getUndoStack()); + macro.push (modifyData.release()); + macro.push (modifyCell.release()); } else mDocument.getUndoStack().push (modifyData.release()); @@ -194,9 +194,7 @@ void CSMWorld::CommandDispatcher::executeDelete() int columnIndex = model.findColumnIndex (Columns::ColumnId_Id); - if (rows.size()>1) - mDocument.getUndoStack().beginMacro (tr ("Delete multiple records")); - + CommandMacro macro (mDocument.getUndoStack(), rows.size()>1 ? "Delete multiple records" : ""); for (std::vector::const_iterator iter (rows.begin()); iter!=rows.end(); ++iter) { std::string id = model.data (model.getModelIndex (*iter, columnIndex)). @@ -204,7 +202,7 @@ void CSMWorld::CommandDispatcher::executeDelete() if (mId.getType() == UniversalId::Type_Referenceables) { - mDocument.getUndoStack().push ( new CSMWorld::DeleteCommand (model, id, + macro.push (new CSMWorld::DeleteCommand (model, id, static_cast(model.data (model.index ( model.getModelIndex (id, columnIndex).row(), model.findColumnIndex (CSMWorld::Columns::ColumnId_RecordType))).toInt()))); @@ -212,9 +210,6 @@ void CSMWorld::CommandDispatcher::executeDelete() else mDocument.getUndoStack().push (new CSMWorld::DeleteCommand (model, id)); } - - if (rows.size()>1) - mDocument.getUndoStack().endMacro(); } void CSMWorld::CommandDispatcher::executeRevert() @@ -231,25 +226,19 @@ void CSMWorld::CommandDispatcher::executeRevert() int columnIndex = model.findColumnIndex (Columns::ColumnId_Id); - if (rows.size()>1) - mDocument.getUndoStack().beginMacro (tr ("Revert multiple records")); - + CommandMacro macro (mDocument.getUndoStack(), rows.size()>1 ? "Revert multiple records" : ""); for (std::vector::const_iterator iter (rows.begin()); iter!=rows.end(); ++iter) { std::string id = model.data (model.getModelIndex (*iter, columnIndex)). toString().toUtf8().constData(); - mDocument.getUndoStack().push (new CSMWorld::RevertCommand (model, id)); + macro.push (new CSMWorld::RevertCommand (model, id)); } - - if (rows.size()>1) - mDocument.getUndoStack().endMacro(); } void CSMWorld::CommandDispatcher::executeExtendedDelete() { - if (mExtendedTypes.size()>1) - mDocument.getUndoStack().beginMacro (tr ("Extended delete of multiple records")); + CommandMacro macro (mDocument.getUndoStack(), mExtendedTypes.size()>1 ? tr ("Extended delete of multiple records") : ""); for (std::vector::const_iterator iter (mExtendedTypes.begin()); iter!=mExtendedTypes.end(); ++iter) @@ -276,20 +265,15 @@ void CSMWorld::CommandDispatcher::executeExtendedDelete() Misc::StringUtils::lowerCase (record.get().mCell))) continue; - mDocument.getUndoStack().push ( - new CSMWorld::DeleteCommand (model, record.get().mId)); + macro.push (new CSMWorld::DeleteCommand (model, record.get().mId)); } } } - - if (mExtendedTypes.size()>1) - mDocument.getUndoStack().endMacro(); } void CSMWorld::CommandDispatcher::executeExtendedRevert() { - if (mExtendedTypes.size()>1) - mDocument.getUndoStack().beginMacro (tr ("Extended revert of multiple records")); + CommandMacro macro (mDocument.getUndoStack(), mExtendedTypes.size()>1 ? tr ("Extended revert of multiple records") : ""); for (std::vector::const_iterator iter (mExtendedTypes.begin()); iter!=mExtendedTypes.end(); ++iter) @@ -313,12 +297,8 @@ void CSMWorld::CommandDispatcher::executeExtendedRevert() Misc::StringUtils::lowerCase (record.get().mCell))) continue; - mDocument.getUndoStack().push ( - new CSMWorld::RevertCommand (model, record.get().mId)); + macro.push (new CSMWorld::RevertCommand (model, record.get().mId)); } } } - - if (mExtendedTypes.size()>1) - mDocument.getUndoStack().endMacro(); } diff --git a/apps/opencs/model/world/commandmacro.cpp b/apps/opencs/model/world/commandmacro.cpp new file mode 100644 index 0000000000..0bd74cbe21 --- /dev/null +++ b/apps/opencs/model/world/commandmacro.cpp @@ -0,0 +1,26 @@ + +#include "commandmacro.hpp" + +#include +#include + +CSMWorld::CommandMacro::CommandMacro (QUndoStack& undoStack, const QString& description) +: mUndoStack (undoStack), mDescription (description), mStarted (false) +{} + +CSMWorld::CommandMacro::~CommandMacro() +{ + if (mStarted) + mUndoStack.endMacro(); +} + +void CSMWorld::CommandMacro::push (QUndoCommand *command) +{ + if (!mStarted) + { + mUndoStack.beginMacro (mDescription.isEmpty() ? command->text() : mDescription); + mStarted = true; + } + + mUndoStack.push (command); +} diff --git a/apps/opencs/model/world/commandmacro.hpp b/apps/opencs/model/world/commandmacro.hpp new file mode 100644 index 0000000000..b1f6301d9a --- /dev/null +++ b/apps/opencs/model/world/commandmacro.hpp @@ -0,0 +1,34 @@ +#ifndef CSM_WOLRD_COMMANDMACRO_H +#define CSM_WOLRD_COMMANDMACRO_H + +class QUndoStack; +class QUndoCommand; + +#include + +namespace CSMWorld +{ + class CommandMacro + { + QUndoStack& mUndoStack; + QString mDescription; + bool mStarted; + + /// not implemented + CommandMacro (const CommandMacro&); + + /// not implemented + CommandMacro& operator= (const CommandMacro&); + + public: + + /// If \a description is empty, the description of the first command is used. + CommandMacro (QUndoStack& undoStack, const QString& description = ""); + + ~CommandMacro(); + + void push (QUndoCommand *command); + }; +} + +#endif diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index d510cd1038..5f94223765 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -8,13 +8,16 @@ #include #include +#include "cellcoordinates.hpp" +#include "idcollection.hpp" #include "idtable.hpp" #include "idtree.hpp" #include "nestedtablewrapper.hpp" +#include "pathgrid.hpp" CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelIndex& index, const QVariant& new_, QUndoCommand* parent) -: QUndoCommand (parent), mModel (&model), mIndex (index), mNew (new_), mHasRecordState(false) + : QUndoCommand (parent), mModel (&model), mIndex (index), mNew (new_), mHasRecordState(false), mOldRecordState(CSMWorld::RecordBase::State_BaseOnly) { if (QAbstractProxyModel *proxy = dynamic_cast (&model)) { @@ -68,9 +71,6 @@ void CSMWorld::ModifyCommand::undo() void CSMWorld::CreateCommand::applyModifications() { - for (std::map::const_iterator iter (mValues.begin()); iter!=mValues.end(); ++iter) - mModel.setData (mModel.getModelIndex (mId, iter->first), iter->second); - if (!mNestedValues.empty()) { CSMWorld::IdTree *tree = dynamic_cast(&mModel); @@ -114,7 +114,7 @@ void CSMWorld::CreateCommand::setType (UniversalId::Type type) void CSMWorld::CreateCommand::redo() { - mModel.addRecord (mId, mType); + mModel.addRecordWithData (mId, mValues, mType); applyModifications(); } @@ -238,6 +238,29 @@ void CSMWorld::CloneCommand::undo() mModel.removeRow (mModel.getModelIndex (mId, 0).row()); } +CSMWorld::CreatePathgridCommand::CreatePathgridCommand(IdTable& model, const std::string& id, QUndoCommand *parent) + : CreateCommand(model, id, parent) +{ + setType(UniversalId::Type_Pathgrid); +} + +void CSMWorld::CreatePathgridCommand::redo() +{ + CreateCommand::redo(); + + Record record = static_cast& >(mModel.getRecord(mId)); + record.get().blank(); + record.get().mCell = mId; + + std::pair coords = CellCoordinates::fromId(mId); + if (coords.second) + { + record.get().mData.mX = coords.first.getX(); + record.get().mData.mY = coords.first.getY(); + } + + mModel.setRecord(mId, record, mType); +} CSMWorld::UpdateCellCommand::UpdateCellCommand (IdTable& model, int row, QUndoCommand *parent) : QUndoCommand (parent), mModel (model), mRow (row) diff --git a/apps/opencs/model/world/commands.hpp b/apps/opencs/model/world/commands.hpp index 23ffccbd7e..b54a1d5acb 100644 --- a/apps/opencs/model/world/commands.hpp +++ b/apps/opencs/model/world/commands.hpp @@ -153,6 +153,15 @@ namespace CSMWorld virtual void undo(); }; + class CreatePathgridCommand : public CreateCommand + { + public: + + CreatePathgridCommand(IdTable& model, const std::string& id, QUndoCommand *parent = 0); + + virtual void redo(); + }; + /// \brief Update cell ID according to x/y-coordinates /// /// \note The new value will be calculated in the first call to redo instead of the diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 1f98b24757..8b0a9b384d 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -10,6 +10,8 @@ #include #include +#include + #include "idtable.hpp" #include "idtree.hpp" #include "columnimp.hpp" @@ -59,10 +61,13 @@ int CSMWorld::Data::count (RecordBase::State state, const CollectionBase& collec return number; } -CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager) +CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager, const Fallback::Map* fallback, const boost::filesystem::path& resDir) : mEncoder (encoding), mPathgrids (mCells), mRefs (mCells), - mResourcesManager (resourcesManager), mReader (0), mDialogue (0), mReaderIndex(0), mResourceSystem(new Resource::ResourceSystem(resourcesManager.getVFS())) + mResourcesManager (resourcesManager), mFallbackMap(fallback), + mReader (0), mDialogue (0), mReaderIndex(0), mResourceSystem(new Resource::ResourceSystem(resourcesManager.getVFS())) { + mResourceSystem->getSceneManager()->setShaderPath((resDir / "shaders").string()); + int index = 0; mGlobals.addColumn (new StringIdColumn); @@ -176,6 +181,14 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mRegions.addColumn (new NameColumn); mRegions.addColumn (new MapColourColumn); mRegions.addColumn (new SleepListColumn); + // Region Weather + mRegions.addColumn (new NestedParentColumn (Columns::ColumnId_RegionWeather)); + index = mRegions.getColumns()-1; + mRegions.addAdapter (std::make_pair(&mRegions.getColumn(index), new RegionWeatherAdapter ())); + mRegions.getNestableColumn(index)->addColumn( + new NestedChildColumn (Columns::ColumnId_WeatherName, ColumnBase::Display_String, false)); + mRegions.getNestableColumn(index)->addColumn( + new NestedChildColumn (Columns::ColumnId_WeatherChance, ColumnBase::Display_UnsignedInteger8)); // Region Sounds mRegions.addColumn (new NestedParentColumn (Columns::ColumnId_RegionSounds)); index = mRegions.getColumns()-1; @@ -270,7 +283,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc new NestedChildColumn (Columns::ColumnId_InfoCondFunc, ColumnBase::Display_InfoCondFunc)); // FIXME: don't have dynamic value enum delegate, use Display_String for now mTopicInfos.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_InfoCondVar, ColumnBase::Display_String)); + new NestedChildColumn (Columns::ColumnId_InfoCondVar, ColumnBase::Display_InfoCondVar)); mTopicInfos.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_InfoCondComp, ColumnBase::Display_InfoCondComp)); mTopicInfos.getNestableColumn(index)->addColumn( @@ -933,7 +946,7 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) bool unhandledRecord = false; - switch (n.val) + switch (n.intval) { case ESM::REC_GLOB: mGlobals.load (*mReader, mBase); break; case ESM::REC_GMST: mGmsts.load (*mReader, mBase); break; @@ -1045,7 +1058,7 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) else { mTopics.load (record, mBase); - mDialogue = &mTopics.getRecord (record.mId).get(); + mDialogue = &mTopics.getRecord (record.mId).get(); } } @@ -1201,3 +1214,8 @@ const VFS::Manager* CSMWorld::Data::getVFS() const { return mResourcesManager.getVFS(); } + +const Fallback::Map* CSMWorld::Data::getFallbackMap() const +{ + return mFallbackMap; +} diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index c6623279a6..f0c3dcd417 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -58,6 +58,11 @@ namespace VFS class Manager; } +namespace Fallback +{ + class Map; +} + namespace ESM { class ESMReader; @@ -104,6 +109,7 @@ namespace CSMWorld IdCollection mFilters; Collection mMetaData; const ResourcesManager& mResourcesManager; + const Fallback::Map* mFallbackMap; std::vector mModels; std::map mModelIndex; ESM::ESMReader *mReader; @@ -132,12 +138,14 @@ namespace CSMWorld public: - Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager); + Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager, const Fallback::Map* fallback, const boost::filesystem::path& resDir); virtual ~Data(); const VFS::Manager* getVFS() const; + const Fallback::Map* getFallbackMap() const; + boost::shared_ptr getResourceSystem(); boost::shared_ptr getResourceSystem() const; diff --git a/apps/opencs/model/world/defaultgmsts.cpp b/apps/opencs/model/world/defaultgmsts.cpp new file mode 100644 index 0000000000..f44e98411c --- /dev/null +++ b/apps/opencs/model/world/defaultgmsts.cpp @@ -0,0 +1,2336 @@ +#include "defaultgmsts.hpp" + +#include + +const float FInf = std::numeric_limits::infinity(); +const float FEps = std::numeric_limits::epsilon(); + +const int IMax = std::numeric_limits::max(); +const int IMin = std::numeric_limits::min(); + +const char* CSMWorld::DefaultGmsts::Floats[CSMWorld::DefaultGmsts::FloatCount] = +{ + "fAIFleeFleeMult", + "fAIFleeHealthMult", + "fAIMagicSpellMult", + "fAIMeleeArmorMult", + "fAIMeleeSummWeaponMult", + "fAIMeleeWeaponMult", + "fAIRangeMagicSpellMult", + "fAIRangeMeleeWeaponMult", + "fAlarmRadius", + "fAthleticsRunBonus", + "fAudioDefaultMaxDistance", + "fAudioDefaultMinDistance", + "fAudioMaxDistanceMult", + "fAudioMinDistanceMult", + "fAudioVoiceDefaultMaxDistance", + "fAudioVoiceDefaultMinDistance", + "fAutoPCSpellChance", + "fAutoSpellChance", + "fBargainOfferBase", + "fBargainOfferMulti", + "fBarterGoldResetDelay", + "fBaseRunMultiplier", + "fBlockStillBonus", + "fBribe1000Mod", + "fBribe100Mod", + "fBribe10Mod", + "fCombatAngleXY", + "fCombatAngleZ", + "fCombatArmorMinMult", + "fCombatBlockLeftAngle", + "fCombatBlockRightAngle", + "fCombatCriticalStrikeMult", + "fCombatDelayCreature", + "fCombatDelayNPC", + "fCombatDistance", + "fCombatDistanceWerewolfMod", + "fCombatForceSideAngle", + "fCombatInvisoMult", + "fCombatKODamageMult", + "fCombatTorsoSideAngle", + "fCombatTorsoStartPercent", + "fCombatTorsoStopPercent", + "fConstantEffectMult", + "fCorpseClearDelay", + "fCorpseRespawnDelay", + "fCrimeGoldDiscountMult", + "fCrimeGoldTurnInMult", + "fCrimeStealing", + "fDamageStrengthBase", + "fDamageStrengthMult", + "fDifficultyMult", + "fDiseaseXferChance", + "fDispAttacking", + "fDispBargainFailMod", + "fDispBargainSuccessMod", + "fDispCrimeMod", + "fDispDiseaseMod", + "fDispFactionMod", + "fDispFactionRankBase", + "fDispFactionRankMult", + "fDispositionMod", + "fDispPersonalityBase", + "fDispPersonalityMult", + "fDispPickPocketMod", + "fDispRaceMod", + "fDispStealing", + "fDispWeaponDrawn", + "fEffectCostMult", + "fElementalShieldMult", + "fEnchantmentChanceMult", + "fEnchantmentConstantChanceMult", + "fEnchantmentConstantDurationMult", + "fEnchantmentMult", + "fEnchantmentValueMult", + "fEncumberedMoveEffect", + "fEncumbranceStrMult", + "fEndFatigueMult", + "fFallAcroBase", + "fFallAcroMult", + "fFallDamageDistanceMin", + "fFallDistanceBase", + "fFallDistanceMult", + "fFatigueAttackBase", + "fFatigueAttackMult", + "fFatigueBase", + "fFatigueBlockBase", + "fFatigueBlockMult", + "fFatigueJumpBase", + "fFatigueJumpMult", + "fFatigueMult", + "fFatigueReturnBase", + "fFatigueReturnMult", + "fFatigueRunBase", + "fFatigueRunMult", + "fFatigueSneakBase", + "fFatigueSneakMult", + "fFatigueSpellBase", + "fFatigueSpellCostMult", + "fFatigueSpellMult", + "fFatigueSwimRunBase", + "fFatigueSwimRunMult", + "fFatigueSwimWalkBase", + "fFatigueSwimWalkMult", + "fFightDispMult", + "fFightDistanceMultiplier", + "fFightStealing", + "fFleeDistance", + "fGreetDistanceReset", + "fHandtoHandHealthPer", + "fHandToHandReach", + "fHoldBreathEndMult", + "fHoldBreathTime", + "fIdleChanceMultiplier", + "fIngredientMult", + "fInteriorHeadTrackMult", + "fJumpAcrobaticsBase", + "fJumpAcroMultiplier", + "fJumpEncumbranceBase", + "fJumpEncumbranceMultiplier", + "fJumpMoveBase", + "fJumpMoveMult", + "fJumpRunMultiplier", + "fKnockDownMult", + "fLevelMod", + "fLevelUpHealthEndMult", + "fLightMaxMod", + "fLuckMod", + "fMagesGuildTravel", + "fMagicCreatureCastDelay", + "fMagicDetectRefreshRate", + "fMagicItemConstantMult", + "fMagicItemCostMult", + "fMagicItemOnceMult", + "fMagicItemPriceMult", + "fMagicItemRechargePerSecond", + "fMagicItemStrikeMult", + "fMagicItemUsedMult", + "fMagicStartIconBlink", + "fMagicSunBlockedMult", + "fMajorSkillBonus", + "fMaxFlySpeed", + "fMaxHandToHandMult", + "fMaxHeadTrackDistance", + "fMaxWalkSpeed", + "fMaxWalkSpeedCreature", + "fMedMaxMod", + "fMessageTimePerChar", + "fMinFlySpeed", + "fMinHandToHandMult", + "fMinorSkillBonus", + "fMinWalkSpeed", + "fMinWalkSpeedCreature", + "fMiscSkillBonus", + "fNPCbaseMagickaMult", + "fNPCHealthBarFade", + "fNPCHealthBarTime", + "fPCbaseMagickaMult", + "fPerDieRollMult", + "fPersonalityMod", + "fPerTempMult", + "fPickLockMult", + "fPickPocketMod", + "fPotionMinUsefulDuration", + "fPotionStrengthMult", + "fPotionT1DurMult", + "fPotionT1MagMult", + "fPotionT4BaseStrengthMult", + "fPotionT4EquipStrengthMult", + "fProjectileMaxSpeed", + "fProjectileMinSpeed", + "fProjectileThrownStoreChance", + "fRepairAmountMult", + "fRepairMult", + "fReputationMod", + "fRestMagicMult", + "fSeriousWoundMult", + "fSleepRandMod", + "fSleepRestMod", + "fSneakBootMult", + "fSneakDistanceBase", + "fSneakDistanceMultiplier", + "fSneakNoViewMult", + "fSneakSkillMult", + "fSneakSpeedMultiplier", + "fSneakUseDelay", + "fSneakUseDist", + "fSneakViewMult", + "fSoulGemMult", + "fSpecialSkillBonus", + "fSpellMakingValueMult", + "fSpellPriceMult", + "fSpellValueMult", + "fStromWalkMult", + "fStromWindSpeed", + "fSuffocationDamage", + "fSwimHeightScale", + "fSwimRunAthleticsMult", + "fSwimRunBase", + "fSwimWalkAthleticsMult", + "fSwimWalkBase", + "fSwingBlockBase", + "fSwingBlockMult", + "fTargetSpellMaxSpeed", + "fThrownWeaponMaxSpeed", + "fThrownWeaponMinSpeed", + "fTrapCostMult", + "fTravelMult", + "fTravelTimeMult", + "fUnarmoredBase1", + "fUnarmoredBase2", + "fVanityDelay", + "fVoiceIdleOdds", + "fWaterReflectUpdateAlways", + "fWaterReflectUpdateSeldom", + "fWeaponDamageMult", + "fWeaponFatigueBlockMult", + "fWeaponFatigueMult", + "fWereWolfAcrobatics", + "fWereWolfAgility", + "fWereWolfAlchemy", + "fWereWolfAlteration", + "fWereWolfArmorer", + "fWereWolfAthletics", + "fWereWolfAxe", + "fWereWolfBlock", + "fWereWolfBluntWeapon", + "fWereWolfConjuration", + "fWereWolfDestruction", + "fWereWolfEnchant", + "fWereWolfEndurance", + "fWereWolfFatigue", + "fWereWolfHandtoHand", + "fWereWolfHealth", + "fWereWolfHeavyArmor", + "fWereWolfIllusion", + "fWereWolfIntellegence", + "fWereWolfLightArmor", + "fWereWolfLongBlade", + "fWereWolfLuck", + "fWereWolfMagicka", + "fWereWolfMarksman", + "fWereWolfMediumArmor", + "fWereWolfMerchantile", + "fWereWolfMysticism", + "fWereWolfPersonality", + "fWereWolfRestoration", + "fWereWolfRunMult", + "fWereWolfSecurity", + "fWereWolfShortBlade", + "fWereWolfSilverWeaponDamageMult", + "fWereWolfSneak", + "fWereWolfSpear", + "fWereWolfSpeechcraft", + "fWereWolfSpeed", + "fWereWolfStrength", + "fWereWolfUnarmored", + "fWereWolfWillPower", + "fWortChanceValue" +}; + +const char * CSMWorld::DefaultGmsts::Ints[CSMWorld::DefaultGmsts::IntCount] = +{ + "i1stPersonSneakDelta", + "iAlarmAttack", + "iAlarmKilling", + "iAlarmPickPocket", + "iAlarmStealing", + "iAlarmTresspass", + "iAlchemyMod", + "iAutoPCSpellMax", + "iAutoRepFacMod", + "iAutoRepLevMod", + "iAutoSpellAlterationMax", + "iAutoSpellAttSkillMin", + "iAutoSpellConjurationMax", + "iAutoSpellDestructionMax", + "iAutoSpellIllusionMax", + "iAutoSpellMysticismMax", + "iAutoSpellRestorationMax", + "iAutoSpellTimesCanCast", + "iBarterFailDisposition", + "iBarterSuccessDisposition", + "iBaseArmorSkill", + "iBlockMaxChance", + "iBlockMinChance", + "iBootsWeight", + "iCrimeAttack", + "iCrimeKilling", + "iCrimePickPocket", + "iCrimeThreshold", + "iCrimeThresholdMultiplier", + "iCrimeTresspass", + "iCuirassWeight", + "iDaysinPrisonMod", + "iDispAttackMod", + "iDispKilling", + "iDispTresspass", + "iFightAlarmMult", + "iFightAttack", + "iFightAttacking", + "iFightDistanceBase", + "iFightKilling", + "iFightPickpocket", + "iFightTrespass", + "iFlee", + "iGauntletWeight", + "iGreavesWeight", + "iGreetDistanceMultiplier", + "iGreetDuration", + "iHelmWeight", + "iKnockDownOddsBase", + "iKnockDownOddsMult", + "iLevelUp01Mult", + "iLevelUp02Mult", + "iLevelUp03Mult", + "iLevelUp04Mult", + "iLevelUp05Mult", + "iLevelUp06Mult", + "iLevelUp07Mult", + "iLevelUp08Mult", + "iLevelUp09Mult", + "iLevelUp10Mult", + "iLevelupMajorMult", + "iLevelupMajorMultAttribute", + "iLevelupMinorMult", + "iLevelupMinorMultAttribute", + "iLevelupMiscMultAttriubte", + "iLevelupSpecialization", + "iLevelupTotal", + "iMagicItemChargeConst", + "iMagicItemChargeOnce", + "iMagicItemChargeStrike", + "iMagicItemChargeUse", + "iMaxActivateDist", + "iMaxInfoDist", + "iMonthsToRespawn", + "iNumberCreatures", + "iPauldronWeight", + "iPerMinChance", + "iPerMinChange", + "iPickMaxChance", + "iPickMinChance", + "iShieldWeight", + "iSoulAmountForConstantEffect", + "iTrainingMod", + "iVoiceAttackOdds", + "iVoiceHitOdds", + "iWereWolfBounty", + "iWereWolfFightMod", + "iWereWolfFleeMod", + "iWereWolfLevelToAttack" +}; + +const char * CSMWorld::DefaultGmsts::Strings[CSMWorld::DefaultGmsts::StringCount] = +{ + "s3dAudio", + "s3dHardware", + "s3dSoftware", + "sAbsorb", + "sAcrobat", + "sActivate", + "sActivateXbox", + "sActorInCombat", + "sAdmire", + "sAdmireFail", + "sAdmireSuccess", + "sAgent", + "sAgiDesc", + "sAIDistance", + "sAlembic", + "sAllTab", + "sAlways", + "sAlways_Run", + "sand", + "sApparatus", + "sApparelTab", + "sArcher", + "sArea", + "sAreaDes", + "sArmor", + "sArmorRating", + "sAsk", + "sAssassin", + "sAt", + "sAttack", + "sAttributeAgility", + "sAttributeEndurance", + "sAttributeIntelligence", + "sAttributeListTitle", + "sAttributeLuck", + "sAttributePersonality", + "sAttributesMenu1", + "sAttributeSpeed", + "sAttributeStrength", + "sAttributeWillpower", + "sAudio", + "sAuto_Run", + "sBack", + "sBackspace", + "sBackXbox", + "sBarbarian", + "sBard", + "sBarter", + "sBarterDialog1", + "sBarterDialog10", + "sBarterDialog11", + "sBarterDialog12", + "sBarterDialog2", + "sBarterDialog3", + "sBarterDialog4", + "sBarterDialog5", + "sBarterDialog6", + "sBarterDialog7", + "sBarterDialog8", + "sBarterDialog9", + "sBattlemage", + "sBestAttack", + "sBirthSign", + "sBirthsignmenu1", + "sBirthsignmenu2", + "sBlocks", + "sBonusSkillTitle", + "sBookPageOne", + "sBookPageTwo", + "sBookSkillMessage", + "sBounty", + "sBreath", + "sBribe 10 Gold", + "sBribe 100 Gold", + "sBribe 1000 Gold", + "sBribeFail", + "sBribeSuccess", + "sBuy", + "sBye", + "sCalcinator", + "sCancel", + "sCantEquipWeapWarning", + "sCastCost", + "sCaughtStealingMessage", + "sCenter", + "sChangedMastersMsg", + "sCharges", + "sChooseClassMenu1", + "sChooseClassMenu2", + "sChooseClassMenu3", + "sChooseClassMenu4", + "sChop", + "sClass", + "sClassChoiceMenu1", + "sClassChoiceMenu2", + "sClassChoiceMenu3", + "sClose", + "sCompanionShare", + "sCompanionWarningButtonOne", + "sCompanionWarningButtonTwo", + "sCompanionWarningMessage", + "sCondition", + "sConsoleTitle", + "sContainer", + "sContentsMessage1", + "sContentsMessage2", + "sContentsMessage3", + "sControlerVibration", + "sControls", + "sControlsMenu1", + "sControlsMenu2", + "sControlsMenu3", + "sControlsMenu4", + "sControlsMenu5", + "sControlsMenu6", + "sCostChance", + "sCostCharge", + "sCreate", + "sCreateClassMenu1", + "sCreateClassMenu2", + "sCreateClassMenu3", + "sCreateClassMenuHelp1", + "sCreateClassMenuHelp2", + "sCreateClassMenuWarning", + "sCreatedEffects", + "sCrimeHelp", + "sCrimeMessage", + "sCrouch_Sneak", + "sCrouchXbox", + "sCrusader", + "sCursorOff", + "sCustom", + "sCustomClassName", + "sDamage", + "sDark_Gamma", + "sDay", + "sDefaultCellname", + "sDelete", + "sDeleteGame", + "sDeleteNote", + "sDeleteSpell", + "sDeleteSpellError", + "sDetail_Level", + "sDialogMenu1", + "sDialogText1Xbox", + "sDialogText2Xbox", + "sDialogText3Xbox", + "sDifficulty", + "sDisposeCorpseFail", + "sDisposeofCorpse", + "sDone", + "sDoYouWantTo", + "sDrain", + "sDrop", + "sDuration", + "sDurationDes", + "sEasy", + "sEditNote", + "sEffectAbsorbAttribute", + "sEffectAbsorbFatigue", + "sEffectAbsorbHealth", + "sEffectAbsorbSkill", + "sEffectAbsorbSpellPoints", + "sEffectAlmsiviIntervention", + "sEffectBlind", + "sEffectBoundBattleAxe", + "sEffectBoundBoots", + "sEffectBoundCuirass", + "sEffectBoundDagger", + "sEffectBoundGloves", + "sEffectBoundHelm", + "sEffectBoundLongbow", + "sEffectBoundLongsword", + "sEffectBoundMace", + "sEffectBoundShield", + "sEffectBoundSpear", + "sEffectBurden", + "sEffectCalmCreature", + "sEffectCalmHumanoid", + "sEffectChameleon", + "sEffectCharm", + "sEffectCommandCreatures", + "sEffectCommandHumanoids", + "sEffectCorpus", + "sEffectCureBlightDisease", + "sEffectCureCommonDisease", + "sEffectCureCorprusDisease", + "sEffectCureParalyzation", + "sEffectCurePoison", + "sEffectDamageAttribute", + "sEffectDamageFatigue", + "sEffectDamageHealth", + "sEffectDamageMagicka", + "sEffectDamageSkill", + "sEffectDemoralizeCreature", + "sEffectDemoralizeHumanoid", + "sEffectDetectAnimal", + "sEffectDetectEnchantment", + "sEffectDetectKey", + "sEffectDisintegrateArmor", + "sEffectDisintegrateWeapon", + "sEffectDispel", + "sEffectDivineIntervention", + "sEffectDrainAttribute", + "sEffectDrainFatigue", + "sEffectDrainHealth", + "sEffectDrainSkill", + "sEffectDrainSpellpoints", + "sEffectExtraSpell", + "sEffectFeather", + "sEffectFireDamage", + "sEffectFireShield", + "sEffectFortifyAttackBonus", + "sEffectFortifyAttribute", + "sEffectFortifyFatigue", + "sEffectFortifyHealth", + "sEffectFortifyMagickaMultiplier", + "sEffectFortifySkill", + "sEffectFortifySpellpoints", + "sEffectFrenzyCreature", + "sEffectFrenzyHumanoid", + "sEffectFrostDamage", + "sEffectFrostShield", + "sEffectInvisibility", + "sEffectJump", + "sEffectLevitate", + "sEffectLight", + "sEffectLightningShield", + "sEffectLock", + "sEffectMark", + "sEffectNightEye", + "sEffectOpen", + "sEffectParalyze", + "sEffectPoison", + "sEffectRallyCreature", + "sEffectRallyHumanoid", + "sEffectRecall", + "sEffectReflect", + "sEffectRemoveCurse", + "sEffectResistBlightDisease", + "sEffectResistCommonDisease", + "sEffectResistCorprusDisease", + "sEffectResistFire", + "sEffectResistFrost", + "sEffectResistMagicka", + "sEffectResistNormalWeapons", + "sEffectResistParalysis", + "sEffectResistPoison", + "sEffectResistShock", + "sEffectRestoreAttribute", + "sEffectRestoreFatigue", + "sEffectRestoreHealth", + "sEffectRestoreSkill", + "sEffectRestoreSpellPoints", + "sEffects", + "sEffectSanctuary", + "sEffectShield", + "sEffectShockDamage", + "sEffectSilence", + "sEffectSlowFall", + "sEffectSoultrap", + "sEffectSound", + "sEffectSpellAbsorption", + "sEffectStuntedMagicka", + "sEffectSummonAncestralGhost", + "sEffectSummonBonelord", + "sEffectSummonCenturionSphere", + "sEffectSummonClannfear", + "sEffectSummonCreature01", + "sEffectSummonCreature02", + "sEffectSummonCreature03", + "sEffectSummonCreature04", + "sEffectSummonCreature05", + "sEffectSummonDaedroth", + "sEffectSummonDremora", + "sEffectSummonFabricant", + "sEffectSummonFlameAtronach", + "sEffectSummonFrostAtronach", + "sEffectSummonGoldensaint", + "sEffectSummonGreaterBonewalker", + "sEffectSummonHunger", + "sEffectSummonLeastBonewalker", + "sEffectSummonScamp", + "sEffectSummonSkeletalMinion", + "sEffectSummonStormAtronach", + "sEffectSummonWingedTwilight", + "sEffectSunDamage", + "sEffectSwiftSwim", + "sEffectTelekinesis", + "sEffectTurnUndead", + "sEffectVampirism", + "sEffectWaterBreathing", + "sEffectWaterWalking", + "sEffectWeaknessToBlightDisease", + "sEffectWeaknessToCommonDisease", + "sEffectWeaknessToCorprusDisease", + "sEffectWeaknessToFire", + "sEffectWeaknessToFrost", + "sEffectWeaknessToMagicka", + "sEffectWeaknessToNormalWeapons", + "sEffectWeaknessToPoison", + "sEffectWeaknessToShock", + "sEnableJoystick", + "sEnchanting", + "sEnchantItems", + "sEnchantmentHelp1", + "sEnchantmentHelp10", + "sEnchantmentHelp2", + "sEnchantmentHelp3", + "sEnchantmentHelp4", + "sEnchantmentHelp5", + "sEnchantmentHelp6", + "sEnchantmentHelp7", + "sEnchantmentHelp8", + "sEnchantmentHelp9", + "sEnchantmentMenu1", + "sEnchantmentMenu10", + "sEnchantmentMenu11", + "sEnchantmentMenu12", + "sEnchantmentMenu2", + "sEnchantmentMenu3", + "sEnchantmentMenu4", + "sEnchantmentMenu5", + "sEnchantmentMenu6", + "sEnchantmentMenu7", + "sEnchantmentMenu8", + "sEnchantmentMenu9", + "sEncumbrance", + "sEndDesc", + "sEquip", + "sExitGame", + "sExpelled", + "sExpelledMessage", + "sFace", + "sFaction", + "sFar", + "sFast", + "sFatDesc", + "sFatigue", + "sFavoriteSkills", + "sfeet", + "sFileSize", + "sfootarea", + "sFootsteps", + "sfor", + "sFortify", + "sForward", + "sForwardXbox", + "sFull", + "sGame", + "sGameWithoutLauncherXbox", + "sGamma_Correction", + "sGeneralMastPlugMismatchMsg", + "sGold", + "sGoodbye", + "sGoverningAttribute", + "sgp", + "sHair", + "sHard", + "sHeal", + "sHealer", + "sHealth", + "sHealthDesc", + "sHealthPerHourOfRest", + "sHealthPerLevel", + "sHeavy", + "sHigh", + "sin", + "sInfo", + "sInfoRefusal", + "sIngredients", + "sInPrisonTitle", + "sInputMenu1", + "sIntDesc", + "sIntimidate", + "sIntimidateFail", + "sIntimidateSuccess", + "sInvalidSaveGameMsg", + "sInvalidSaveGameMsgXBOX", + "sInventory", + "sInventoryMenu1", + "sInventoryMessage1", + "sInventoryMessage2", + "sInventoryMessage3", + "sInventoryMessage4", + "sInventoryMessage5", + "sInventorySelectNoIngredients", + "sInventorySelectNoItems", + "sInventorySelectNoSoul", + "sItem", + "sItemCastConstant", + "sItemCastOnce", + "sItemCastWhenStrikes", + "sItemCastWhenUsed", + "sItemName", + "sJournal", + "sJournalCmd", + "sJournalEntry", + "sJournalXbox", + "sJoystickHatShort", + "sJoystickNotFound", + "sJoystickShort", + "sJump", + "sJumpXbox", + "sKeyName_00", + "sKeyName_01", + "sKeyName_02", + "sKeyName_03", + "sKeyName_04", + "sKeyName_05", + "sKeyName_06", + "sKeyName_07", + "sKeyName_08", + "sKeyName_09", + "sKeyName_0A", + "sKeyName_0B", + "sKeyName_0C", + "sKeyName_0D", + "sKeyName_0E", + "sKeyName_0F", + "sKeyName_10", + "sKeyName_11", + "sKeyName_12", + "sKeyName_13", + "sKeyName_14", + "sKeyName_15", + "sKeyName_16", + "sKeyName_17", + "sKeyName_18", + "sKeyName_19", + "sKeyName_1A", + "sKeyName_1B", + "sKeyName_1C", + "sKeyName_1D", + "sKeyName_1E", + "sKeyName_1F", + "sKeyName_20", + "sKeyName_21", + "sKeyName_22", + "sKeyName_23", + "sKeyName_24", + "sKeyName_25", + "sKeyName_26", + "sKeyName_27", + "sKeyName_28", + "sKeyName_29", + "sKeyName_2A", + "sKeyName_2B", + "sKeyName_2C", + "sKeyName_2D", + "sKeyName_2E", + "sKeyName_2F", + "sKeyName_30", + "sKeyName_31", + "sKeyName_32", + "sKeyName_33", + "sKeyName_34", + "sKeyName_35", + "sKeyName_36", + "sKeyName_37", + "sKeyName_38", + "sKeyName_39", + "sKeyName_3A", + "sKeyName_3B", + "sKeyName_3C", + "sKeyName_3D", + "sKeyName_3E", + "sKeyName_3F", + "sKeyName_40", + "sKeyName_41", + "sKeyName_42", + "sKeyName_43", + "sKeyName_44", + "sKeyName_45", + "sKeyName_46", + "sKeyName_47", + "sKeyName_48", + "sKeyName_49", + "sKeyName_4A", + "sKeyName_4B", + "sKeyName_4C", + "sKeyName_4D", + "sKeyName_4E", + "sKeyName_4F", + "sKeyName_50", + "sKeyName_51", + "sKeyName_52", + "sKeyName_53", + "sKeyName_54", + "sKeyName_55", + "sKeyName_56", + "sKeyName_57", + "sKeyName_58", + "sKeyName_59", + "sKeyName_5A", + "sKeyName_5B", + "sKeyName_5C", + "sKeyName_5D", + "sKeyName_5E", + "sKeyName_5F", + "sKeyName_60", + "sKeyName_61", + "sKeyName_62", + "sKeyName_63", + "sKeyName_64", + "sKeyName_65", + "sKeyName_66", + "sKeyName_67", + "sKeyName_68", + "sKeyName_69", + "sKeyName_6A", + "sKeyName_6B", + "sKeyName_6C", + "sKeyName_6D", + "sKeyName_6E", + "sKeyName_6F", + "sKeyName_70", + "sKeyName_71", + "sKeyName_72", + "sKeyName_73", + "sKeyName_74", + "sKeyName_75", + "sKeyName_76", + "sKeyName_77", + "sKeyName_78", + "sKeyName_79", + "sKeyName_7A", + "sKeyName_7B", + "sKeyName_7C", + "sKeyName_7D", + "sKeyName_7E", + "sKeyName_7F", + "sKeyName_80", + "sKeyName_81", + "sKeyName_82", + "sKeyName_83", + "sKeyName_84", + "sKeyName_85", + "sKeyName_86", + "sKeyName_87", + "sKeyName_88", + "sKeyName_89", + "sKeyName_8A", + "sKeyName_8B", + "sKeyName_8C", + "sKeyName_8D", + "sKeyName_8E", + "sKeyName_8F", + "sKeyName_90", + "sKeyName_91", + "sKeyName_92", + "sKeyName_93", + "sKeyName_94", + "sKeyName_95", + "sKeyName_96", + "sKeyName_97", + "sKeyName_98", + "sKeyName_99", + "sKeyName_9A", + "sKeyName_9B", + "sKeyName_9C", + "sKeyName_9D", + "sKeyName_9E", + "sKeyName_9F", + "sKeyName_A0", + "sKeyName_A1", + "sKeyName_A2", + "sKeyName_A3", + "sKeyName_A4", + "sKeyName_A5", + "sKeyName_A6", + "sKeyName_A7", + "sKeyName_A8", + "sKeyName_A9", + "sKeyName_AA", + "sKeyName_AB", + "sKeyName_AC", + "sKeyName_AD", + "sKeyName_AE", + "sKeyName_AF", + "sKeyName_B0", + "sKeyName_B1", + "sKeyName_B2", + "sKeyName_B3", + "sKeyName_B4", + "sKeyName_B5", + "sKeyName_B6", + "sKeyName_B7", + "sKeyName_B8", + "sKeyName_B9", + "sKeyName_BA", + "sKeyName_BB", + "sKeyName_BC", + "sKeyName_BD", + "sKeyName_BE", + "sKeyName_BF", + "sKeyName_C0", + "sKeyName_C1", + "sKeyName_C2", + "sKeyName_C3", + "sKeyName_C4", + "sKeyName_C5", + "sKeyName_C6", + "sKeyName_C7", + "sKeyName_C8", + "sKeyName_C9", + "sKeyName_CA", + "sKeyName_CB", + "sKeyName_CC", + "sKeyName_CD", + "sKeyName_CE", + "sKeyName_CF", + "sKeyName_D0", + "sKeyName_D1", + "sKeyName_D2", + "sKeyName_D3", + "sKeyName_D4", + "sKeyName_D5", + "sKeyName_D6", + "sKeyName_D7", + "sKeyName_D8", + "sKeyName_D9", + "sKeyName_DA", + "sKeyName_DB", + "sKeyName_DC", + "sKeyName_DD", + "sKeyName_DE", + "sKeyName_DF", + "sKeyName_E0", + "sKeyName_E1", + "sKeyName_E2", + "sKeyName_E3", + "sKeyName_E4", + "sKeyName_E5", + "sKeyName_E6", + "sKeyName_E7", + "sKeyName_E8", + "sKeyName_E9", + "sKeyName_EA", + "sKeyName_EB", + "sKeyName_EC", + "sKeyName_ED", + "sKeyName_EE", + "sKeyName_EF", + "sKeyName_F0", + "sKeyName_F1", + "sKeyName_F2", + "sKeyName_F3", + "sKeyName_F4", + "sKeyName_F5", + "sKeyName_F6", + "sKeyName_F7", + "sKeyName_F8", + "sKeyName_F9", + "sKeyName_FA", + "sKeyName_FB", + "sKeyName_FC", + "sKeyName_FD", + "sKeyName_FE", + "sKeyName_FF", + "sKeyUsed", + "sKilledEssential", + "sKnight", + "sLeft", + "sLess", + "sLevel", + "sLevelProgress", + "sLevels", + "sLevelUp", + "sLevelUpMenu1", + "sLevelUpMenu2", + "sLevelUpMenu3", + "sLevelUpMenu4", + "sLevelUpMsg", + "sLevitateDisabled", + "sLight", + "sLight_Gamma", + "sLoadFailedMessage", + "sLoadGame", + "sLoadingErrorsMsg", + "sLoadingMessage1", + "sLoadingMessage14", + "sLoadingMessage15", + "sLoadingMessage2", + "sLoadingMessage3", + "sLoadingMessage4", + "sLoadingMessage5", + "sLoadingMessage9", + "sLoadLastSaveMsg", + "sLocal", + "sLockFail", + "sLockImpossible", + "sLockLevel", + "sLockSuccess", + "sLookDownXbox", + "sLookUpXbox", + "sLow", + "sLucDesc", + "sMagDesc", + "sMage", + "sMagic", + "sMagicAncestralGhostID", + "sMagicBonelordID", + "sMagicBoundBattleAxeID", + "sMagicBoundBootsID", + "sMagicBoundCuirassID", + "sMagicBoundDaggerID", + "sMagicBoundHelmID", + "sMagicBoundLeftGauntletID", + "sMagicBoundLongbowID", + "sMagicBoundLongswordID", + "sMagicBoundMaceID", + "sMagicBoundRightGauntletID", + "sMagicBoundShieldID", + "sMagicBoundSpearID", + "sMagicCannotRecast", + "sMagicCenturionSphereID", + "sMagicClannfearID", + "sMagicContractDisease", + "sMagicCorprusWorsens", + "sMagicCreature01ID", + "sMagicCreature02ID", + "sMagicCreature03ID", + "sMagicCreature04ID", + "sMagicCreature05ID", + "sMagicDaedrothID", + "sMagicDremoraID", + "sMagicEffects", + "sMagicFabricantID", + "sMagicFlameAtronachID", + "sMagicFrostAtronachID", + "sMagicGoldenSaintID", + "sMagicGreaterBonewalkerID", + "sMagicHungerID", + "sMagicInsufficientCharge", + "sMagicInsufficientSP", + "sMagicInvalidEffect", + "sMagicInvalidTarget", + "sMagicItem", + "sMagicLeastBonewalkerID", + "sMagicLockSuccess", + "sMagicMenu", + "sMagicOpenSuccess", + "sMagicPCResisted", + "sMagicScampID", + "sMagicSelectTitle", + "sMagicSkeletalMinionID", + "sMagicSkillFail", + "sMagicStormAtronachID", + "sMagicTab", + "sMagicTargetResisted", + "sMagicTargetResistsWeapons", + "sMagicWingedTwilightID", + "sMagnitude", + "sMagnitudeDes", + "sMake Enchantment", + "sMap", + "sMaster", + "sMastPlugMismatchMsg", + "sMaximumSaveGameMessage", + "sMaxSale", + "sMedium", + "sMenu_Help_Delay", + "sMenu_Mode", + "sMenuModeXbox", + "sMenuNextXbox", + "sMenuPrevXbox", + "sMenus", + "sMessage1", + "sMessage2", + "sMessage3", + "sMessage4", + "sMessage5", + "sMessageQuestionAnswer1", + "sMessageQuestionAnswer2", + "sMessageQuestionAnswer3", + "sMiscTab", + "sMissingMastersMsg", + "sMonk", + "sMonthEveningstar", + "sMonthFirstseed", + "sMonthFrostfall", + "sMonthHeartfire", + "sMonthLastseed", + "sMonthMidyear", + "sMonthMorningstar", + "sMonthRainshand", + "sMonthSecondseed", + "sMonthSunsdawn", + "sMonthSunsdusk", + "sMonthSunsheight", + "sMore", + "sMortar", + "sMouse", + "sMouseFlip", + "sMouseWheelDownShort", + "sMouseWheelUpShort", + "sMove", + "sMoveDownXbox", + "sMoveUpXbox", + "sMusic", + "sName", + "sNameTitle", + "sNear", + "sNeedOneSkill", + "sNeedTwoSkills", + "sNewGame", + "sNext", + "sNextRank", + "sNextSpell", + "sNextSpellXbox", + "sNextWeapon", + "sNextWeaponXbox", + "sNightblade", + "sNo", + "sNoName", + "sNone", + "sNotifyMessage1", + "sNotifyMessage10", + "sNotifyMessage11", + "sNotifyMessage12", + "sNotifyMessage13", + "sNotifyMessage14", + "sNotifyMessage15", + "sNotifyMessage16", + "sNotifyMessage16_a", + "sNotifyMessage17", + "sNotifyMessage18", + "sNotifyMessage19", + "sNotifyMessage2", + "sNotifyMessage20", + "sNotifyMessage21", + "sNotifyMessage22", + "sNotifyMessage23", + "sNotifyMessage24", + "sNotifyMessage25", + "sNotifyMessage26", + "sNotifyMessage27", + "sNotifyMessage28", + "sNotifyMessage29", + "sNotifyMessage3", + "sNotifyMessage30", + "sNotifyMessage31", + "sNotifyMessage32", + "sNotifyMessage33", + "sNotifyMessage34", + "sNotifyMessage35", + "sNotifyMessage36", + "sNotifyMessage37", + "sNotifyMessage38", + "sNotifyMessage39", + "sNotifyMessage4", + "sNotifyMessage40", + "sNotifyMessage41", + "sNotifyMessage42", + "sNotifyMessage43", + "sNotifyMessage44", + "sNotifyMessage45", + "sNotifyMessage46", + "sNotifyMessage47", + "sNotifyMessage48", + "sNotifyMessage49", + "sNotifyMessage4XBOX", + "sNotifyMessage5", + "sNotifyMessage50", + "sNotifyMessage51", + "sNotifyMessage52", + "sNotifyMessage53", + "sNotifyMessage54", + "sNotifyMessage55", + "sNotifyMessage56", + "sNotifyMessage57", + "sNotifyMessage58", + "sNotifyMessage59", + "sNotifyMessage6", + "sNotifyMessage60", + "sNotifyMessage61", + "sNotifyMessage62", + "sNotifyMessage63", + "sNotifyMessage64", + "sNotifyMessage65", + "sNotifyMessage66", + "sNotifyMessage67", + "sNotifyMessage6a", + "sNotifyMessage7", + "sNotifyMessage8", + "sNotifyMessage9", + "sOff", + "sOffer", + "sOfferMenuTitle", + "sOK", + "sOn", + "sOnce", + "sOneHanded", + "sOnetypeEffectMessage", + "sonword", + "sOptions", + "sOptionsMenuXbox", + "spercent", + "sPerDesc", + "sPersuasion", + "sPersuasionMenuTitle", + "sPickUp", + "sPilgrim", + "spoint", + "spoints", + "sPotionSuccess", + "sPowerAlreadyUsed", + "sPowers", + "sPreferences", + "sPrefs", + "sPrev", + "sPrevSpell", + "sPrevSpellXbox", + "sPrevWeapon", + "sPrevWeaponXbox", + "sProfitValue", + "sQuality", + "sQuanityMenuMessage01", + "sQuanityMenuMessage02", + "sQuestionDeleteSpell", + "sQuestionMark", + "sQuick0Xbox", + "sQuick10Cmd", + "sQuick1Cmd", + "sQuick2Cmd", + "sQuick3Cmd", + "sQuick4Cmd", + "sQuick4Xbox", + "sQuick5Cmd", + "sQuick5Xbox", + "sQuick6Cmd", + "sQuick6Xbox", + "sQuick7Cmd", + "sQuick7Xbox", + "sQuick8Cmd", + "sQuick8Xbox", + "sQuick9Cmd", + "sQuick9Xbox", + "sQuick_Save", + "sQuickLoadCmd", + "sQuickLoadXbox", + "sQuickMenu", + "sQuickMenu1", + "sQuickMenu2", + "sQuickMenu3", + "sQuickMenu4", + "sQuickMenu5", + "sQuickMenu6", + "sQuickMenuInstruc", + "sQuickMenuTitle", + "sQuickSaveCmd", + "sQuickSaveXbox", + "sRace", + "sRaceMenu1", + "sRaceMenu2", + "sRaceMenu3", + "sRaceMenu4", + "sRaceMenu5", + "sRaceMenu6", + "sRaceMenu7", + "sRacialTraits", + "sRange", + "sRangeDes", + "sRangeSelf", + "sRangeTarget", + "sRangeTouch", + "sReady_Magic", + "sReady_Weapon", + "sReadyItemXbox", + "sReadyMagicXbox", + "sRechargeEnchantment", + "sRender_Distance", + "sRepair", + "sRepairFailed", + "sRepairServiceTitle", + "sRepairSuccess", + "sReputation", + "sResChangeWarning", + "sRest", + "sRestIllegal", + "sRestKey", + "sRestMenu1", + "sRestMenu2", + "sRestMenu3", + "sRestMenu4", + "sRestMenuXbox", + "sRestore", + "sRetort", + "sReturnToGame", + "sRight", + "sRogue", + "sRun", + "sRunXbox", + "sSave", + "sSaveGame", + "sSaveGameDenied", + "sSaveGameFailed", + "sSaveGameNoMemory", + "sSaveGameTooBig", + "sSaveMenu1", + "sSaveMenuHelp01", + "sSaveMenuHelp02", + "sSaveMenuHelp03", + "sSaveMenuHelp04", + "sSaveMenuHelp05", + "sSaveMenuHelp06", + "sSchool", + "sSchoolAlteration", + "sSchoolConjuration", + "sSchoolDestruction", + "sSchoolIllusion", + "sSchoolMysticism", + "sSchoolRestoration", + "sScout", + "sScrolldown", + "sScrollup", + "ssecond", + "sseconds", + "sSeldom", + "sSelect", + "sSell", + "sSellerGold", + "sService", + "sServiceRefusal", + "sServiceRepairTitle", + "sServiceSpellsTitle", + "sServiceTrainingTitle", + "sServiceTrainingWords", + "sServiceTravelTitle", + "sSetValueMessage01", + "sSex", + "sShadows", + "sShadowText", + "sShift", + "sSkill", + "sSkillAcrobatics", + "sSkillAlchemy", + "sSkillAlteration", + "sSkillArmorer", + "sSkillAthletics", + "sSkillAxe", + "sSkillBlock", + "sSkillBluntweapon", + "sSkillClassMajor", + "sSkillClassMinor", + "sSkillClassMisc", + "sSkillConjuration", + "sSkillDestruction", + "sSkillEnchant", + "sSkillHandtohand", + "sSkillHeavyarmor", + "sSkillIllusion", + "sSkillLightarmor", + "sSkillLongblade", + "sSkillMarksman", + "sSkillMaxReached", + "sSkillMediumarmor", + "sSkillMercantile", + "sSkillMysticism", + "sSkillProgress", + "sSkillRestoration", + "sSkillSecurity", + "sSkillShortblade", + "sSkillsMenu1", + "sSkillsMenuReputationHelp", + "sSkillSneak", + "sSkillSpear", + "sSkillSpeechcraft", + "sSkillUnarmored", + "sSlash", + "sSleepInterrupt", + "sSlideLeftXbox", + "sSlideRightXbox", + "sSlow", + "sSorceror", + "sSoulGem", + "sSoulGemsWithSouls", + "sSoultrapSuccess", + "sSpace", + "sSpdDesc", + "sSpecialization", + "sSpecializationCombat", + "sSpecializationMagic", + "sSpecializationMenu1", + "sSpecializationStealth", + "sSpellmaking", + "sSpellmakingHelp1", + "sSpellmakingHelp2", + "sSpellmakingHelp3", + "sSpellmakingHelp4", + "sSpellmakingHelp5", + "sSpellmakingHelp6", + "sSpellmakingMenu1", + "sSpellmakingMenuTitle", + "sSpells", + "sSpellServiceTitle", + "sSpellsword", + "sStartCell", + "sStartCellError", + "sStartError", + "sStats", + "sStrafe", + "sStrDesc", + "sStrip", + "sSubtitles", + "sSystemMenuXbox", + "sTake", + "sTakeAll", + "sTargetCriticalStrike", + "sTaunt", + "sTauntFail", + "sTauntSuccess", + "sTeleportDisabled", + "sThief", + "sThrust", + "sTo", + "sTogglePOVCmd", + "sTogglePOVXbox", + "sToggleRunXbox", + "sTopics", + "sTotalCost", + "sTotalSold", + "sTraining", + "sTrainingServiceTitle", + "sTraits", + "sTransparency_Menu", + "sTrapFail", + "sTrapImpossible", + "sTrapped", + "sTrapSuccess", + "sTravel", + "sTravelServiceTitle", + "sTurn", + "sTurnLeftXbox", + "sTurnRightXbox", + "sTwoHanded", + "sType", + "sTypeAbility", + "sTypeBlightDisease", + "sTypeCurse", + "sTypeDisease", + "sTypePower", + "sTypeSpell", + "sUnequip", + "sUnlocked", + "sUntilHealed", + "sUse", + "sUserDefinedClass", + "sUses", + "sUseXbox", + "sValue", + "sVideo", + "sVideoWarning", + "sVoice", + "sWait", + "sWarrior", + "sWaterReflectUpdate", + "sWaterTerrainReflect", + "sWeaponTab", + "sWeight", + "sWerewolfAlarmMessage", + "sWerewolfPopup", + "sWerewolfRefusal", + "sWerewolfRestMessage", + "sWilDesc", + "sWitchhunter", + "sWorld", + "sWornTab", + "sXStrafe", + "sXTimes", + "sXTimesINT", + "sYes", + "sYourGold" +}; + +const char * CSMWorld::DefaultGmsts::OptionalFloats[CSMWorld::DefaultGmsts::OptionalFloatCount] = +{ + "fCombatDistanceWerewolfMod", + "fFleeDistance", + "fWereWolfAcrobatics", + "fWereWolfAgility", + "fWereWolfAlchemy", + "fWereWolfAlteration", + "fWereWolfArmorer", + "fWereWolfAthletics", + "fWereWolfAxe", + "fWereWolfBlock", + "fWereWolfBluntWeapon", + "fWereWolfConjuration", + "fWereWolfDestruction", + "fWereWolfEnchant", + "fWereWolfEndurance", + "fWereWolfFatigue", + "fWereWolfHandtoHand", + "fWereWolfHealth", + "fWereWolfHeavyArmor", + "fWereWolfIllusion", + "fWereWolfIntellegence", + "fWereWolfLightArmor", + "fWereWolfLongBlade", + "fWereWolfLuck", + "fWereWolfMagicka", + "fWereWolfMarksman", + "fWereWolfMediumArmor", + "fWereWolfMerchantile", + "fWereWolfMysticism", + "fWereWolfPersonality", + "fWereWolfRestoration", + "fWereWolfRunMult", + "fWereWolfSecurity", + "fWereWolfShortBlade", + "fWereWolfSilverWeaponDamageMult", + "fWereWolfSneak", + "fWereWolfSpear", + "fWereWolfSpeechcraft", + "fWereWolfSpeed", + "fWereWolfStrength", + "fWereWolfUnarmored", + "fWereWolfWillPower" +}; + +const char * CSMWorld::DefaultGmsts::OptionalInts[CSMWorld::DefaultGmsts::OptionalIntCount] = +{ + "iWereWolfBounty", + "iWereWolfFightMod", + "iWereWolfFleeMod", + "iWereWolfLevelToAttack" +}; + +const char * CSMWorld::DefaultGmsts::OptionalStrings[CSMWorld::DefaultGmsts::OptionalStringCount] = +{ + "sCompanionShare", + "sCompanionWarningButtonOne", + "sCompanionWarningButtonTwo", + "sCompanionWarningMessage", + "sDeleteNote", + "sEditNote", + "sEffectSummonCreature01", + "sEffectSummonCreature02", + "sEffectSummonCreature03", + "sEffectSummonCreature04", + "sEffectSummonCreature05", + "sEffectSummonFabricant", + "sLevitateDisabled", + "sMagicCreature01ID", + "sMagicCreature02ID", + "sMagicCreature03ID", + "sMagicCreature04ID", + "sMagicCreature05ID", + "sMagicFabricantID", + "sMaxSale", + "sProfitValue", + "sTeleportDisabled", + "sWerewolfAlarmMessage", + "sWerewolfPopup", + "sWerewolfRefusal", + "sWerewolfRestMessage" +}; + +const float CSMWorld::DefaultGmsts::FloatsDefaultValues[CSMWorld::DefaultGmsts::FloatCount] = +{ + 0.3, // fAIFleeFleeMult + 7.0, // fAIFleeHealthMult + 3.0, // fAIMagicSpellMult + 1.0, // fAIMeleeArmorMult + 1.0, // fAIMeleeSummWeaponMult + 2.0, // fAIMeleeWeaponMult + 5.0, // fAIRangeMagicSpellMult + 5.0, // fAIRangeMeleeWeaponMult + 2000.0, // fAlarmRadius + 1.0, // fAthleticsRunBonus + 40.0, // fAudioDefaultMaxDistance + 5.0, // fAudioDefaultMinDistance + 50.0, // fAudioMaxDistanceMult + 20.0, // fAudioMinDistanceMult + 60.0, // fAudioVoiceDefaultMaxDistance + 10.0, // fAudioVoiceDefaultMinDistance + 50.0, // fAutoPCSpellChance + 80.0, // fAutoSpellChance + 50.0, // fBargainOfferBase + -4.0, // fBargainOfferMulti + 24.0, // fBarterGoldResetDelay + 1.75, // fBaseRunMultiplier + 1.25, // fBlockStillBonus + 150.0, // fBribe1000Mod + 75.0, // fBribe100Mod + 35.0, // fBribe10Mod + 60.0, // fCombatAngleXY + 60.0, // fCombatAngleZ + 0.25, // fCombatArmorMinMult + -90.0, // fCombatBlockLeftAngle + 30.0, // fCombatBlockRightAngle + 4.0, // fCombatCriticalStrikeMult + 0.1, // fCombatDelayCreature + 0.1, // fCombatDelayNPC + 128.0, // fCombatDistance + 0.3, // fCombatDistanceWerewolfMod + 30.0, // fCombatForceSideAngle + 0.2, // fCombatInvisoMult + 1.5, // fCombatKODamageMult + 45.0, // fCombatTorsoSideAngle + 0.3, // fCombatTorsoStartPercent + 0.8, // fCombatTorsoStopPercent + 15.0, // fConstantEffectMult + 72.0, // fCorpseClearDelay + 72.0, // fCorpseRespawnDelay + 0.5, // fCrimeGoldDiscountMult + 0.9, // fCrimeGoldTurnInMult + 1.0, // fCrimeStealing + 0.5, // fDamageStrengthBase + 0.1, // fDamageStrengthMult + 5.0, // fDifficultyMult + 2.5, // fDiseaseXferChance + -10.0, // fDispAttacking + -1.0, // fDispBargainFailMod + 1.0, // fDispBargainSuccessMod + 0.0, // fDispCrimeMod + -10.0, // fDispDiseaseMod + 3.0, // fDispFactionMod + 1.0, // fDispFactionRankBase + 0.5, // fDispFactionRankMult + 1.0, // fDispositionMod + 50.0, // fDispPersonalityBase + 0.5, // fDispPersonalityMult + -25.0, // fDispPickPocketMod + 5.0, // fDispRaceMod + -0.5, // fDispStealing + -5.0, // fDispWeaponDrawn + 0.5, // fEffectCostMult + 0.1, // fElementalShieldMult + 3.0, // fEnchantmentChanceMult + 0.5, // fEnchantmentConstantChanceMult + 100.0, // fEnchantmentConstantDurationMult + 0.1, // fEnchantmentMult + 1000.0, // fEnchantmentValueMult + 0.3, // fEncumberedMoveEffect + 5.0, // fEncumbranceStrMult + 0.04, // fEndFatigueMult + 0.25, // fFallAcroBase + 0.01, // fFallAcroMult + 400.0, // fFallDamageDistanceMin + 0.0, // fFallDistanceBase + 0.07, // fFallDistanceMult + 2.0, // fFatigueAttackBase + 0.0, // fFatigueAttackMult + 1.25, // fFatigueBase + 4.0, // fFatigueBlockBase + 0.0, // fFatigueBlockMult + 5.0, // fFatigueJumpBase + 0.0, // fFatigueJumpMult + 0.5, // fFatigueMult + 2.5, // fFatigueReturnBase + 0.02, // fFatigueReturnMult + 5.0, // fFatigueRunBase + 2.0, // fFatigueRunMult + 1.5, // fFatigueSneakBase + 1.5, // fFatigueSneakMult + 0.0, // fFatigueSpellBase + 0.0, // fFatigueSpellCostMult + 0.0, // fFatigueSpellMult + 7.0, // fFatigueSwimRunBase + 0.0, // fFatigueSwimRunMult + 2.5, // fFatigueSwimWalkBase + 0.0, // fFatigueSwimWalkMult + 0.2, // fFightDispMult + 0.005, // fFightDistanceMultiplier + 50.0, // fFightStealing + 3000.0, // fFleeDistance + 512.0, // fGreetDistanceReset + 0.1, // fHandtoHandHealthPer + 1.0, // fHandToHandReach + 0.5, // fHoldBreathEndMult + 20.0, // fHoldBreathTime + 0.75, // fIdleChanceMultiplier + 1.0, // fIngredientMult + 0.5, // fInteriorHeadTrackMult + 128.0, // fJumpAcrobaticsBase + 4.0, // fJumpAcroMultiplier + 0.5, // fJumpEncumbranceBase + 1.0, // fJumpEncumbranceMultiplier + 0.5, // fJumpMoveBase + 0.5, // fJumpMoveMult + 1.0, // fJumpRunMultiplier + 0.5, // fKnockDownMult + 5.0, // fLevelMod + 0.1, // fLevelUpHealthEndMult + 0.6, // fLightMaxMod + 10.0, // fLuckMod + 10.0, // fMagesGuildTravel + 1.5, // fMagicCreatureCastDelay + 0.0167, // fMagicDetectRefreshRate + 1.0, // fMagicItemConstantMult + 1.0, // fMagicItemCostMult + 1.0, // fMagicItemOnceMult + 1.0, // fMagicItemPriceMult + 0.05, // fMagicItemRechargePerSecond + 1.0, // fMagicItemStrikeMult + 1.0, // fMagicItemUsedMult + 3.0, // fMagicStartIconBlink + 0.5, // fMagicSunBlockedMult + 0.75, // fMajorSkillBonus + 300.0, // fMaxFlySpeed + 0.5, // fMaxHandToHandMult + 400.0, // fMaxHeadTrackDistance + 200.0, // fMaxWalkSpeed + 300.0, // fMaxWalkSpeedCreature + 0.9, // fMedMaxMod + 0.1, // fMessageTimePerChar + 5.0, // fMinFlySpeed + 0.1, // fMinHandToHandMult + 1.0, // fMinorSkillBonus + 100.0, // fMinWalkSpeed + 5.0, // fMinWalkSpeedCreature + 1.25, // fMiscSkillBonus + 2.0, // fNPCbaseMagickaMult + 0.5, // fNPCHealthBarFade + 3.0, // fNPCHealthBarTime + 1.0, // fPCbaseMagickaMult + 0.3, // fPerDieRollMult + 5.0, // fPersonalityMod + 1.0, // fPerTempMult + -1.0, // fPickLockMult + 0.3, // fPickPocketMod + 20.0, // fPotionMinUsefulDuration + 0.5, // fPotionStrengthMult + 0.5, // fPotionT1DurMult + 1.5, // fPotionT1MagMult + 20.0, // fPotionT4BaseStrengthMult + 12.0, // fPotionT4EquipStrengthMult + 3000.0, // fProjectileMaxSpeed + 400.0, // fProjectileMinSpeed + 25.0, // fProjectileThrownStoreChance + 3.0, // fRepairAmountMult + 1.0, // fRepairMult + 1.0, // fReputationMod + 0.15, // fRestMagicMult + 0.0, // fSeriousWoundMult + 0.25, // fSleepRandMod + 0.3, // fSleepRestMod + -1.0, // fSneakBootMult + 0.5, // fSneakDistanceBase + 0.002, // fSneakDistanceMultiplier + 0.5, // fSneakNoViewMult + 1.0, // fSneakSkillMult + 0.75, // fSneakSpeedMultiplier + 1.0, // fSneakUseDelay + 500.0, // fSneakUseDist + 1.5, // fSneakViewMult + 3.0, // fSoulGemMult + 0.8, // fSpecialSkillBonus + 7.0, // fSpellMakingValueMult + 2.0, // fSpellPriceMult + 10.0, // fSpellValueMult + 0.25, // fStromWalkMult + 0.7, // fStromWindSpeed + 3.0, // fSuffocationDamage + 0.9, // fSwimHeightScale + 0.1, // fSwimRunAthleticsMult + 0.5, // fSwimRunBase + 0.02, // fSwimWalkAthleticsMult + 0.5, // fSwimWalkBase + 1.0, // fSwingBlockBase + 1.0, // fSwingBlockMult + 1000.0, // fTargetSpellMaxSpeed + 1000.0, // fThrownWeaponMaxSpeed + 300.0, // fThrownWeaponMinSpeed + 0.0, // fTrapCostMult + 4000.0, // fTravelMult + 16000.0,// fTravelTimeMult + 0.1, // fUnarmoredBase1 + 0.065, // fUnarmoredBase2 + 30.0, // fVanityDelay + 10.0, // fVoiceIdleOdds + 0.0, // fWaterReflectUpdateAlways + 10.0, // fWaterReflectUpdateSeldom + 0.1, // fWeaponDamageMult + 1.0, // fWeaponFatigueBlockMult + 0.25, // fWeaponFatigueMult + 150.0, // fWereWolfAcrobatics + 150.0, // fWereWolfAgility + 1.0, // fWereWolfAlchemy + 1.0, // fWereWolfAlteration + 1.0, // fWereWolfArmorer + 150.0, // fWereWolfAthletics + 1.0, // fWereWolfAxe + 1.0, // fWereWolfBlock + 1.0, // fWereWolfBluntWeapon + 1.0, // fWereWolfConjuration + 1.0, // fWereWolfDestruction + 1.0, // fWereWolfEnchant + 150.0, // fWereWolfEndurance + 400.0, // fWereWolfFatigue + 100.0, // fWereWolfHandtoHand + 2.0, // fWereWolfHealth + 1.0, // fWereWolfHeavyArmor + 1.0, // fWereWolfIllusion + 1.0, // fWereWolfIntellegence + 1.0, // fWereWolfLightArmor + 1.0, // fWereWolfLongBlade + 1.0, // fWereWolfLuck + 100.0, // fWereWolfMagicka + 1.0, // fWereWolfMarksman + 1.0, // fWereWolfMediumArmor + 1.0, // fWereWolfMerchantile + 1.0, // fWereWolfMysticism + 1.0, // fWereWolfPersonality + 1.0, // fWereWolfRestoration + 1.5, // fWereWolfRunMult + 1.0, // fWereWolfSecurity + 1.0, // fWereWolfShortBlade + 1.5, // fWereWolfSilverWeaponDamageMult + 1.0, // fWereWolfSneak + 1.0, // fWereWolfSpear + 1.0, // fWereWolfSpeechcraft + 150.0, // fWereWolfSpeed + 150.0, // fWereWolfStrength + 100.0, // fWereWolfUnarmored + 1.0, // fWereWolfWillPower + 15.0 // fWortChanceValue +}; + +const int CSMWorld::DefaultGmsts::IntsDefaultValues[CSMWorld::DefaultGmsts::IntCount] = +{ + 10, // i1stPersonSneakDelta + 50, // iAlarmAttack + 90, // iAlarmKilling + 20, // iAlarmPickPocket + 1, // iAlarmStealing + 5, // iAlarmTresspass + 2, // iAlchemyMod + 100, // iAutoPCSpellMax + 2, // iAutoRepFacMod + 0, // iAutoRepLevMod + 5, // iAutoSpellAlterationMax + 70, // iAutoSpellAttSkillMin + 2, // iAutoSpellConjurationMax + 5, // iAutoSpellDestructionMax + 5, // iAutoSpellIllusionMax + 5, // iAutoSpellMysticismMax + 5, // iAutoSpellRestorationMax + 3, // iAutoSpellTimesCanCast + -1, // iBarterFailDisposition + 1, // iBarterSuccessDisposition + 30, // iBaseArmorSkill + 50, // iBlockMaxChance + 10, // iBlockMinChance + 20, // iBootsWeight + 40, // iCrimeAttack + 1000, // iCrimeKilling + 25, // iCrimePickPocket + 1000, // iCrimeThreshold + 10, // iCrimeThresholdMultiplier + 5, // iCrimeTresspass + 30, // iCuirassWeight + 100, // iDaysinPrisonMod + -50, // iDispAttackMod + -50, // iDispKilling + -20, // iDispTresspass + 1, // iFightAlarmMult + 100, // iFightAttack + 50, // iFightAttacking + 20, // iFightDistanceBase + 50, // iFightKilling + 25, // iFightPickpocket + 25, // iFightTrespass + 0, // iFlee + 5, // iGauntletWeight + 15, // iGreavesWeight + 6, // iGreetDistanceMultiplier + 4, // iGreetDuration + 5, // iHelmWeight + 50, // iKnockDownOddsBase + 50, // iKnockDownOddsMult + 2, // iLevelUp01Mult + 2, // iLevelUp02Mult + 2, // iLevelUp03Mult + 2, // iLevelUp04Mult + 3, // iLevelUp05Mult + 3, // iLevelUp06Mult + 3, // iLevelUp07Mult + 4, // iLevelUp08Mult + 4, // iLevelUp09Mult + 5, // iLevelUp10Mult + 1, // iLevelupMajorMult + 1, // iLevelupMajorMultAttribute + 1, // iLevelupMinorMult + 1, // iLevelupMinorMultAttribute + 1, // iLevelupMiscMultAttriubte + 1, // iLevelupSpecialization + 10, // iLevelupTotal + 10, // iMagicItemChargeConst + 1, // iMagicItemChargeOnce + 10, // iMagicItemChargeStrike + 5, // iMagicItemChargeUse + 192, // iMaxActivateDist + 192, // iMaxInfoDist + 4, // iMonthsToRespawn + 1, // iNumberCreatures + 10, // iPauldronWeight + 5, // iPerMinChance + 10, // iPerMinChange + 75, // iPickMaxChance + 5, // iPickMinChance + 15, // iShieldWeight + 400, // iSoulAmountForConstantEffect + 10, // iTrainingMod + 10, // iVoiceAttackOdds + 30, // iVoiceHitOdds + 10000, // iWereWolfBounty + 100, // iWereWolfFightMod + 100, // iWereWolfFleeMod + 20 // iWereWolfLevelToAttack +}; + +const float CSMWorld::DefaultGmsts::FloatLimits[CSMWorld::DefaultGmsts::FloatCount * 2] = +{ + -FInf, FInf, // fAIFleeFleeMult + -FInf, FInf, // fAIFleeHealthMult + -FInf, FInf, // fAIMagicSpellMult + -FInf, FInf, // fAIMeleeArmorMult + -FInf, FInf, // fAIMeleeSummWeaponMult + -FInf, FInf, // fAIMeleeWeaponMult + -FInf, FInf, // fAIRangeMagicSpellMult + -FInf, FInf, // fAIRangeMeleeWeaponMult + 0, FInf, // fAlarmRadius + -FInf, FInf, // fAthleticsRunBonus + 0, FInf, // fAudioDefaultMaxDistance + 0, FInf, // fAudioDefaultMinDistance + 0, FInf, // fAudioMaxDistanceMult + 0, FInf, // fAudioMinDistanceMult + 0, FInf, // fAudioVoiceDefaultMaxDistance + 0, FInf, // fAudioVoiceDefaultMinDistance + 0, FInf, // fAutoPCSpellChance + 0, FInf, // fAutoSpellChance + -FInf, FInf, // fBargainOfferBase + -FInf, 0, // fBargainOfferMulti + -FInf, FInf, // fBarterGoldResetDelay + 0, FInf, // fBaseRunMultiplier + -FInf, FInf, // fBlockStillBonus + 0, FInf, // fBribe1000Mod + 0, FInf, // fBribe100Mod + 0, FInf, // fBribe10Mod + 0, FInf, // fCombatAngleXY + 0, FInf, // fCombatAngleZ + 0, 1, // fCombatArmorMinMult + -180, 0, // fCombatBlockLeftAngle + 0, 180, // fCombatBlockRightAngle + 0, FInf, // fCombatCriticalStrikeMult + 0, FInf, // fCombatDelayCreature + 0, FInf, // fCombatDelayNPC + 0, FInf, // fCombatDistance + -FInf, FInf, // fCombatDistanceWerewolfMod + -FInf, FInf, // fCombatForceSideAngle + 0, FInf, // fCombatInvisoMult + 0, FInf, // fCombatKODamageMult + -FInf, FInf, // fCombatTorsoSideAngle + -FInf, FInf, // fCombatTorsoStartPercent + -FInf, FInf, // fCombatTorsoStopPercent + -FInf, FInf, // fConstantEffectMult + -FInf, FInf, // fCorpseClearDelay + -FInf, FInf, // fCorpseRespawnDelay + 0, 1, // fCrimeGoldDiscountMult + 0, FInf, // fCrimeGoldTurnInMult + 0, FInf, // fCrimeStealing + 0, FInf, // fDamageStrengthBase + 0, FInf, // fDamageStrengthMult + -FInf, FInf, // fDifficultyMult + 0, FInf, // fDiseaseXferChance + -FInf, 0, // fDispAttacking + -FInf, FInf, // fDispBargainFailMod + -FInf, FInf, // fDispBargainSuccessMod + -FInf, 0, // fDispCrimeMod + -FInf, 0, // fDispDiseaseMod + 0, FInf, // fDispFactionMod + 0, FInf, // fDispFactionRankBase + 0, FInf, // fDispFactionRankMult + 0, FInf, // fDispositionMod + 0, FInf, // fDispPersonalityBase + 0, FInf, // fDispPersonalityMult + -FInf, 0, // fDispPickPocketMod + 0, FInf, // fDispRaceMod + -FInf, 0, // fDispStealing + -FInf, 0, // fDispWeaponDrawn + 0, FInf, // fEffectCostMult + 0, FInf, // fElementalShieldMult + FEps, FInf, // fEnchantmentChanceMult + 0, FInf, // fEnchantmentConstantChanceMult + 0, FInf, // fEnchantmentConstantDurationMult + 0, FInf, // fEnchantmentMult + 0, FInf, // fEnchantmentValueMult + 0, FInf, // fEncumberedMoveEffect + 0, FInf, // fEncumbranceStrMult + 0, FInf, // fEndFatigueMult + -FInf, FInf, // fFallAcroBase + 0, FInf, // fFallAcroMult + 0, FInf, // fFallDamageDistanceMin + -FInf, FInf, // fFallDistanceBase + 0, FInf, // fFallDistanceMult + -FInf, FInf, // fFatigueAttackBase + 0, FInf, // fFatigueAttackMult + 0, FInf, // fFatigueBase + 0, FInf, // fFatigueBlockBase + 0, FInf, // fFatigueBlockMult + 0, FInf, // fFatigueJumpBase + 0, FInf, // fFatigueJumpMult + 0, FInf, // fFatigueMult + -FInf, FInf, // fFatigueReturnBase + 0, FInf, // fFatigueReturnMult + -FInf, FInf, // fFatigueRunBase + 0, FInf, // fFatigueRunMult + -FInf, FInf, // fFatigueSneakBase + 0, FInf, // fFatigueSneakMult + -FInf, FInf, // fFatigueSpellBase + -FInf, FInf, // fFatigueSpellCostMult + 0, FInf, // fFatigueSpellMult + -FInf, FInf, // fFatigueSwimRunBase + 0, FInf, // fFatigueSwimRunMult + -FInf, FInf, // fFatigueSwimWalkBase + 0, FInf, // fFatigueSwimWalkMult + -FInf, FInf, // fFightDispMult + -FInf, FInf, // fFightDistanceMultiplier + -FInf, FInf, // fFightStealing + -FInf, FInf, // fFleeDistance + -FInf, FInf, // fGreetDistanceReset + 0, FInf, // fHandtoHandHealthPer + 0, FInf, // fHandToHandReach + -FInf, FInf, // fHoldBreathEndMult + 0, FInf, // fHoldBreathTime + 0, FInf, // fIdleChanceMultiplier + -FInf, FInf, // fIngredientMult + 0, FInf, // fInteriorHeadTrackMult + -FInf, FInf, // fJumpAcrobaticsBase + 0, FInf, // fJumpAcroMultiplier + -FInf, FInf, // fJumpEncumbranceBase + 0, FInf, // fJumpEncumbranceMultiplier + -FInf, FInf, // fJumpMoveBase + 0, FInf, // fJumpMoveMult + 0, FInf, // fJumpRunMultiplier + -FInf, FInf, // fKnockDownMult + 0, FInf, // fLevelMod + 0, FInf, // fLevelUpHealthEndMult + 0, FInf, // fLightMaxMod + 0, FInf, // fLuckMod + 0, FInf, // fMagesGuildTravel + -FInf, FInf, // fMagicCreatureCastDelay + -FInf, FInf, // fMagicDetectRefreshRate + -FInf, FInf, // fMagicItemConstantMult + -FInf, FInf, // fMagicItemCostMult + -FInf, FInf, // fMagicItemOnceMult + -FInf, FInf, // fMagicItemPriceMult + 0, FInf, // fMagicItemRechargePerSecond + -FInf, FInf, // fMagicItemStrikeMult + -FInf, FInf, // fMagicItemUsedMult + 0, FInf, // fMagicStartIconBlink + 0, FInf, // fMagicSunBlockedMult + FEps, FInf, // fMajorSkillBonus + 0, FInf, // fMaxFlySpeed + 0, FInf, // fMaxHandToHandMult + 0, FInf, // fMaxHeadTrackDistance + 0, FInf, // fMaxWalkSpeed + 0, FInf, // fMaxWalkSpeedCreature + 0, FInf, // fMedMaxMod + 0, FInf, // fMessageTimePerChar + 0, FInf, // fMinFlySpeed + 0, FInf, // fMinHandToHandMult + FEps, FInf, // fMinorSkillBonus + 0, FInf, // fMinWalkSpeed + 0, FInf, // fMinWalkSpeedCreature + FEps, FInf, // fMiscSkillBonus + 0, FInf, // fNPCbaseMagickaMult + 0, FInf, // fNPCHealthBarFade + 0, FInf, // fNPCHealthBarTime + 0, FInf, // fPCbaseMagickaMult + 0, FInf, // fPerDieRollMult + 0, FInf, // fPersonalityMod + 0, FInf, // fPerTempMult + -FInf, 0, // fPickLockMult + 0, FInf, // fPickPocketMod + -FInf, FInf, // fPotionMinUsefulDuration + 0, FInf, // fPotionStrengthMult + FEps, FInf, // fPotionT1DurMult + FEps, FInf, // fPotionT1MagMult + -FInf, FInf, // fPotionT4BaseStrengthMult + -FInf, FInf, // fPotionT4EquipStrengthMult + 0, FInf, // fProjectileMaxSpeed + 0, FInf, // fProjectileMinSpeed + 0, FInf, // fProjectileThrownStoreChance + 0, FInf, // fRepairAmountMult + 0, FInf, // fRepairMult + 0, FInf, // fReputationMod + 0, FInf, // fRestMagicMult + -FInf, FInf, // fSeriousWoundMult + 0, FInf, // fSleepRandMod + 0, FInf, // fSleepRestMod + -FInf, 0, // fSneakBootMult + -FInf, FInf, // fSneakDistanceBase + 0, FInf, // fSneakDistanceMultiplier + 0, FInf, // fSneakNoViewMult + 0, FInf, // fSneakSkillMult + 0, FInf, // fSneakSpeedMultiplier + 0, FInf, // fSneakUseDelay + 0, FInf, // fSneakUseDist + 0, FInf, // fSneakViewMult + 0, FInf, // fSoulGemMult + 0, FInf, // fSpecialSkillBonus + 0, FInf, // fSpellMakingValueMult + -FInf, FInf, // fSpellPriceMult + 0, FInf, // fSpellValueMult + 0, FInf, // fStromWalkMult + 0, FInf, // fStromWindSpeed + 0, FInf, // fSuffocationDamage + 0, FInf, // fSwimHeightScale + 0, FInf, // fSwimRunAthleticsMult + 0, FInf, // fSwimRunBase + -FInf, FInf, // fSwimWalkAthleticsMult + -FInf, FInf, // fSwimWalkBase + 0, FInf, // fSwingBlockBase + 0, FInf, // fSwingBlockMult + 0, FInf, // fTargetSpellMaxSpeed + 0, FInf, // fThrownWeaponMaxSpeed + 0, FInf, // fThrownWeaponMinSpeed + 0, FInf, // fTrapCostMult + 0, FInf, // fTravelMult + 0, FInf, // fTravelTimeMult + 0, FInf, // fUnarmoredBase1 + 0, FInf, // fUnarmoredBase2 + 0, FInf, // fVanityDelay + 0, FInf, // fVoiceIdleOdds + -FInf, FInf, // fWaterReflectUpdateAlways + -FInf, FInf, // fWaterReflectUpdateSeldom + 0, FInf, // fWeaponDamageMult + 0, FInf, // fWeaponFatigueBlockMult + 0, FInf, // fWeaponFatigueMult + 0, FInf, // fWereWolfAcrobatics + -FInf, FInf, // fWereWolfAgility + -FInf, FInf, // fWereWolfAlchemy + -FInf, FInf, // fWereWolfAlteration + -FInf, FInf, // fWereWolfArmorer + -FInf, FInf, // fWereWolfAthletics + -FInf, FInf, // fWereWolfAxe + -FInf, FInf, // fWereWolfBlock + -FInf, FInf, // fWereWolfBluntWeapon + -FInf, FInf, // fWereWolfConjuration + -FInf, FInf, // fWereWolfDestruction + -FInf, FInf, // fWereWolfEnchant + -FInf, FInf, // fWereWolfEndurance + -FInf, FInf, // fWereWolfFatigue + -FInf, FInf, // fWereWolfHandtoHand + -FInf, FInf, // fWereWolfHealth + -FInf, FInf, // fWereWolfHeavyArmor + -FInf, FInf, // fWereWolfIllusion + -FInf, FInf, // fWereWolfIntellegence + -FInf, FInf, // fWereWolfLightArmor + -FInf, FInf, // fWereWolfLongBlade + -FInf, FInf, // fWereWolfLuck + -FInf, FInf, // fWereWolfMagicka + -FInf, FInf, // fWereWolfMarksman + -FInf, FInf, // fWereWolfMediumArmor + -FInf, FInf, // fWereWolfMerchantile + -FInf, FInf, // fWereWolfMysticism + -FInf, FInf, // fWereWolfPersonality + -FInf, FInf, // fWereWolfRestoration + 0, FInf, // fWereWolfRunMult + -FInf, FInf, // fWereWolfSecurity + -FInf, FInf, // fWereWolfShortBlade + -FInf, FInf, // fWereWolfSilverWeaponDamageMult + -FInf, FInf, // fWereWolfSneak + -FInf, FInf, // fWereWolfSpear + -FInf, FInf, // fWereWolfSpeechcraft + -FInf, FInf, // fWereWolfSpeed + -FInf, FInf, // fWereWolfStrength + -FInf, FInf, // fWereWolfUnarmored + -FInf, FInf, // fWereWolfWillPower + 0, FInf // fWortChanceValue +}; + +const int CSMWorld::DefaultGmsts::IntLimits[CSMWorld::DefaultGmsts::IntCount * 2] = +{ + IMin, IMax, // i1stPersonSneakDelta + IMin, IMax, // iAlarmAttack + IMin, IMax, // iAlarmKilling + IMin, IMax, // iAlarmPickPocket + IMin, IMax, // iAlarmStealing + IMin, IMax, // iAlarmTresspass + IMin, IMax, // iAlchemyMod + 0, IMax, // iAutoPCSpellMax + IMin, IMax, // iAutoRepFacMod + IMin, IMax, // iAutoRepLevMod + IMin, IMax, // iAutoSpellAlterationMax + 0, IMax, // iAutoSpellAttSkillMin + IMin, IMax, // iAutoSpellConjurationMax + IMin, IMax, // iAutoSpellDestructionMax + IMin, IMax, // iAutoSpellIllusionMax + IMin, IMax, // iAutoSpellMysticismMax + IMin, IMax, // iAutoSpellRestorationMax + 0, IMax, // iAutoSpellTimesCanCast + IMin, 0, // iBarterFailDisposition + 0, IMax, // iBarterSuccessDisposition + 1, IMax, // iBaseArmorSkill + 0, IMax, // iBlockMaxChance + 0, IMax, // iBlockMinChance + 0, IMax, // iBootsWeight + IMin, IMax, // iCrimeAttack + IMin, IMax, // iCrimeKilling + IMin, IMax, // iCrimePickPocket + 0, IMax, // iCrimeThreshold + 0, IMax, // iCrimeThresholdMultiplier + IMin, IMax, // iCrimeTresspass + 0, IMax, // iCuirassWeight + 1, IMax, // iDaysinPrisonMod + IMin, 0, // iDispAttackMod + IMin, 0, // iDispKilling + IMin, 0, // iDispTresspass + IMin, IMax, // iFightAlarmMult + IMin, IMax, // iFightAttack + IMin, IMax, // iFightAttacking + 0, IMax, // iFightDistanceBase + IMin, IMax, // iFightKilling + IMin, IMax, // iFightPickpocket + IMin, IMax, // iFightTrespass + IMin, IMax, // iFlee + 0, IMax, // iGauntletWeight + 0, IMax, // iGreavesWeight + 0, IMax, // iGreetDistanceMultiplier + 0, IMax, // iGreetDuration + 0, IMax, // iHelmWeight + IMin, IMax, // iKnockDownOddsBase + IMin, IMax, // iKnockDownOddsMult + IMin, IMax, // iLevelUp01Mult + IMin, IMax, // iLevelUp02Mult + IMin, IMax, // iLevelUp03Mult + IMin, IMax, // iLevelUp04Mult + IMin, IMax, // iLevelUp05Mult + IMin, IMax, // iLevelUp06Mult + IMin, IMax, // iLevelUp07Mult + IMin, IMax, // iLevelUp08Mult + IMin, IMax, // iLevelUp09Mult + IMin, IMax, // iLevelUp10Mult + IMin, IMax, // iLevelupMajorMult + IMin, IMax, // iLevelupMajorMultAttribute + IMin, IMax, // iLevelupMinorMult + IMin, IMax, // iLevelupMinorMultAttribute + IMin, IMax, // iLevelupMiscMultAttriubte + IMin, IMax, // iLevelupSpecialization + IMin, IMax, // iLevelupTotal + IMin, IMax, // iMagicItemChargeConst + IMin, IMax, // iMagicItemChargeOnce + IMin, IMax, // iMagicItemChargeStrike + IMin, IMax, // iMagicItemChargeUse + IMin, IMax, // iMaxActivateDist + IMin, IMax, // iMaxInfoDist + 0, IMax, // iMonthsToRespawn + 0, IMax, // iNumberCreatures + 0, IMax, // iPauldronWeight + 0, IMax, // iPerMinChance + 0, IMax, // iPerMinChange + 0, IMax, // iPickMaxChance + 0, IMax, // iPickMinChance + 0, IMax, // iShieldWeight + 0, IMax, // iSoulAmountForConstantEffect + 0, IMax, // iTrainingMod + 0, IMax, // iVoiceAttackOdds + 0, IMax, // iVoiceHitOdds + IMin, IMax, // iWereWolfBounty + IMin, IMax, // iWereWolfFightMod + IMin, IMax, // iWereWolfFleeMod + IMin, IMax // iWereWolfLevelToAttack +}; diff --git a/apps/opencs/model/world/defaultgmsts.hpp b/apps/opencs/model/world/defaultgmsts.hpp new file mode 100644 index 0000000000..a2888ed6ac --- /dev/null +++ b/apps/opencs/model/world/defaultgmsts.hpp @@ -0,0 +1,34 @@ +#ifndef CSM_WORLD_DEFAULTGMSTS_H +#define CSM_WORLD_DEFAULTGMSTS_H + +#include + +namespace CSMWorld { + namespace DefaultGmsts { + + const size_t FloatCount = 258; + const size_t IntCount = 89; + const size_t StringCount = 1174; + + const size_t OptionalFloatCount = 42; + const size_t OptionalIntCount = 4; + const size_t OptionalStringCount = 26; + + extern const char* Floats[]; + extern const char * Ints[]; + extern const char * Strings[]; + + extern const char * OptionalFloats[]; + extern const char * OptionalInts[]; + extern const char * OptionalStrings[]; + + extern const float FloatsDefaultValues[]; + extern const int IntsDefaultValues[]; + + extern const float FloatLimits[]; + extern const int IntLimits[]; + + } +} + +#endif diff --git a/apps/opencs/model/world/idcompletionmanager.cpp b/apps/opencs/model/world/idcompletionmanager.cpp index 20cd8652c6..7f3221342b 100644 --- a/apps/opencs/model/world/idcompletionmanager.cpp +++ b/apps/opencs/model/world/idcompletionmanager.cpp @@ -60,6 +60,10 @@ std::vector CSMWorld::IdCompletionManager::getDis { types.push_back(current->first); } + + // Hack for Display_InfoCondVar + types.push_back(CSMWorld::ColumnBase::Display_InfoCondVar); + return types; } @@ -104,7 +108,7 @@ void CSMWorld::IdCompletionManager::generateCompleters(CSMWorld::Data &data) QAbstractItemView *popup = new CSVWidget::CompleterPopup(); completer->setPopup(popup); // The completer takes ownership of the popup completer->setMaxVisibleItems(10); - + mCompleters[current->first] = completer; } } diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index bd1179cea2..9dbe389984 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -2,6 +2,8 @@ #include +#include + #include "collectionbase.hpp" #include "columnbase.hpp" @@ -149,6 +151,23 @@ void CSMWorld::IdTable::addRecord (const std::string& id, UniversalId::Type type endInsertRows(); } +void CSMWorld::IdTable::addRecordWithData (const std::string& id, + const std::map& data, UniversalId::Type type) +{ + int index = mIdCollection->getAppendIndex (id, type); + + beginInsertRows (QModelIndex(), index, index); + + mIdCollection->appendBlankRecord (id, type); + + for (std::map::const_iterator iter (data.begin()); iter!=data.end(); ++iter) + { + mIdCollection->setData(index, iter->first, iter->second); + } + + endInsertRows(); +} + void CSMWorld::IdTable::cloneRecord(const std::string& origin, const std::string& destination, CSMWorld::UniversalId::Type type) @@ -242,7 +261,7 @@ std::pair CSMWorld::IdTable::view (int row) return std::make_pair (UniversalId::Type_None, ""); if (id[0]=='#') - id = "sys::default"; + id = ESM::CellId::sDefaultWorldspace; return std::make_pair (UniversalId (UniversalId::Type_Scene, id), hint); } diff --git a/apps/opencs/model/world/idtable.hpp b/apps/opencs/model/world/idtable.hpp index 9ecba02142..8c2f8a46d8 100644 --- a/apps/opencs/model/world/idtable.hpp +++ b/apps/opencs/model/world/idtable.hpp @@ -53,6 +53,10 @@ namespace CSMWorld void addRecord (const std::string& id, UniversalId::Type type = UniversalId::Type_None); ///< \param type Will be ignored, unless the collection supports multiple record types + void addRecordWithData (const std::string& id, const std::map& data, + UniversalId::Type type = UniversalId::Type_None); + ///< \param type Will be ignored, unless the collection supports multiple record types + void cloneRecord(const std::string& origin, const std::string& destination, UniversalId::Type type = UniversalId::Type_None); diff --git a/apps/opencs/model/world/idtree.cpp b/apps/opencs/model/world/idtree.cpp index 124a94e8ca..7c4551a90c 100644 --- a/apps/opencs/model/world/idtree.cpp +++ b/apps/opencs/model/world/idtree.cpp @@ -264,7 +264,7 @@ void CSMWorld::IdTree::setNestedTable(const QModelIndex& index, const CSMWorld:: CSMWorld::NestedTableWrapperBase* CSMWorld::IdTree::nestedTable(const QModelIndex& index) const { if (!hasChildren(index)) - throw std::logic_error("Tried to retrive nested table, but index has no children"); + throw std::logic_error("Tried to retrieve nested table, but index has no children"); return mNestedCollection->nestedTable(index.row(), index.column()); } diff --git a/apps/opencs/model/world/infoselectwrapper.cpp b/apps/opencs/model/world/infoselectwrapper.cpp new file mode 100644 index 0000000000..d036b1965e --- /dev/null +++ b/apps/opencs/model/world/infoselectwrapper.cpp @@ -0,0 +1,893 @@ +#include "infoselectwrapper.hpp" + +#include +#include +#include + +const size_t CSMWorld::ConstInfoSelectWrapper::RuleMinSize = 5; + +const size_t CSMWorld::ConstInfoSelectWrapper::FunctionPrefixOffset = 1; +const size_t CSMWorld::ConstInfoSelectWrapper::FunctionIndexOffset = 2; +const size_t CSMWorld::ConstInfoSelectWrapper::RelationIndexOffset = 4; +const size_t CSMWorld::ConstInfoSelectWrapper::VarNameOffset = 5; + +const char* CSMWorld::ConstInfoSelectWrapper::FunctionEnumStrings[] = +{ + "Rank Low", + "Rank High", + "Rank Requirement", + "Reputation", + "Health Percent", + "PC Reputation", + "PC Level", + "PC Health Percent", + "PC Magicka", + "PC Fatigue", + "PC Strength", + "PC Block", + "PC Armorer", + "PC Medium Armor", + "PC Heavy Armor", + "PC Blunt Weapon", + "PC Long Blade", + "PC Axe", + "PC Spear", + "PC Athletics", + "PC Enchant", + "PC Detruction", + "PC Alteration", + "PC Illusion", + "PC Conjuration", + "PC Mysticism", + "PC Restoration", + "PC Alchemy", + "PC Unarmored", + "PC Security", + "PC Sneak", + "PC Acrobatics", + "PC Light Armor", + "PC Short Blade", + "PC Marksman", + "PC Merchantile", + "PC Speechcraft", + "PC Hand to Hand", + "PC Sex", + "PC Expelled", + "PC Common Disease", + "PC Blight Disease", + "PC Clothing Modifier", + "PC Crime Level", + "Same Sex", + "Same Race", + "Same Faction", + "Faction Rank Difference", + "Detected", + "Alarmed", + "Choice", + "PC Intelligence", + "PC Willpower", + "PC Agility", + "PC Speed", + "PC Endurance", + "PC Personality", + "PC Luck", + "PC Corpus", + "Weather", + "PC Vampire", + "Level", + "Attacked", + "Talked to PC", + "PC Health", + "Creature Target", + "Friend Hit", + "Fight", + "Hello", + "Alarm", + "Flee", + "Should Attack", + "Werewolf", + "PC Werewolf Kills", + "Global", + "Local", + "Journal", + "Item", + "Dead", + "Not Id", + "Not Faction", + "Not Class", + "Not Race", + "Not Cell", + "Not Local", + 0 +}; + +const char* CSMWorld::ConstInfoSelectWrapper::RelationEnumStrings[] = +{ + "=", + "!=", + ">", + ">=", + "<", + "<=", + 0 +}; + +const char* CSMWorld::ConstInfoSelectWrapper::ComparisonEnumStrings[] = +{ + "Boolean", + "Integer", + "Numeric", + 0 +}; + +// static functions + +std::string CSMWorld::ConstInfoSelectWrapper::convertToString(FunctionName name) +{ + if (name < Function_None) + return FunctionEnumStrings[name]; + else + return "(Invalid Data: Function)"; +} + +std::string CSMWorld::ConstInfoSelectWrapper::convertToString(RelationType type) +{ + if (type < Relation_None) + return RelationEnumStrings[type]; + else + return "(Invalid Data: Relation)"; +} + +std::string CSMWorld::ConstInfoSelectWrapper::convertToString(ComparisonType type) +{ + if (type < Comparison_None) + return ComparisonEnumStrings[type]; + else + return "(Invalid Data: Comparison)"; +} + +// ConstInfoSelectWrapper + +CSMWorld::ConstInfoSelectWrapper::ConstInfoSelectWrapper(const ESM::DialInfo::SelectStruct& select) + : mConstSelect(select) +{ + readRule(); +} + +CSMWorld::ConstInfoSelectWrapper::FunctionName CSMWorld::ConstInfoSelectWrapper::getFunctionName() const +{ + return mFunctionName; +} + +CSMWorld::ConstInfoSelectWrapper::RelationType CSMWorld::ConstInfoSelectWrapper::getRelationType() const +{ + return mRelationType; +} + +CSMWorld::ConstInfoSelectWrapper::ComparisonType CSMWorld::ConstInfoSelectWrapper::getComparisonType() const +{ + return mComparisonType; +} + +bool CSMWorld::ConstInfoSelectWrapper::hasVariable() const +{ + return mHasVariable; +} + +const std::string& CSMWorld::ConstInfoSelectWrapper::getVariableName() const +{ + return mVariableName; +} + +bool CSMWorld::ConstInfoSelectWrapper::conditionIsAlwaysTrue() const +{ + if (!variantTypeIsValid()) + return false; + + if (mComparisonType == Comparison_Boolean || mComparisonType == Comparison_Integer) + { + if (mConstSelect.mValue.getType() == ESM::VT_Float) + return conditionIsAlwaysTrue(getConditionFloatRange(), getValidIntRange()); + else + return conditionIsAlwaysTrue(getConditionIntRange(), getValidIntRange()); + } + else if (mComparisonType == Comparison_Numeric) + { + if (mConstSelect.mValue.getType() == ESM::VT_Float) + return conditionIsAlwaysTrue(getConditionFloatRange(), getValidFloatRange()); + else + return conditionIsAlwaysTrue(getConditionIntRange(), getValidFloatRange()); + } + + return false; +} + +bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue() const +{ + if (!variantTypeIsValid()) + return false; + + if (mComparisonType == Comparison_Boolean || mComparisonType == Comparison_Integer) + { + if (mConstSelect.mValue.getType() == ESM::VT_Float) + return conditionIsNeverTrue(getConditionFloatRange(), getValidIntRange()); + else + return conditionIsNeverTrue(getConditionIntRange(), getValidIntRange()); + } + else if (mComparisonType == Comparison_Numeric) + { + if (mConstSelect.mValue.getType() == ESM::VT_Float) + return conditionIsNeverTrue(getConditionFloatRange(), getValidFloatRange()); + else + return conditionIsNeverTrue(getConditionIntRange(), getValidFloatRange()); + } + + return false; +} + +bool CSMWorld::ConstInfoSelectWrapper::variantTypeIsValid() const +{ + return (mConstSelect.mValue.getType() == ESM::VT_Int || mConstSelect.mValue.getType() == ESM::VT_Float); +} + +const ESM::Variant& CSMWorld::ConstInfoSelectWrapper::getVariant() const +{ + return mConstSelect.mValue; +} + +std::string CSMWorld::ConstInfoSelectWrapper::toString() const +{ + std::ostringstream stream; + stream << convertToString(mFunctionName) << " "; + + if (mHasVariable) + stream << mVariableName << " "; + + stream << convertToString(mRelationType) << " "; + + switch (mConstSelect.mValue.getType()) + { + case ESM::VT_Int: + stream << mConstSelect.mValue.getInteger(); + break; + + case ESM::VT_Float: + stream << mConstSelect.mValue.getFloat(); + break; + + default: + stream << "(Invalid value type)"; + break; + } + + return stream.str(); +} + +void CSMWorld::ConstInfoSelectWrapper::readRule() +{ + if (mConstSelect.mSelectRule.size() < RuleMinSize) + throw std::runtime_error("InfoSelectWrapper: rule is to small"); + + readFunctionName(); + readRelationType(); + readVariableName(); + updateHasVariable(); + updateComparisonType(); +} + +void CSMWorld::ConstInfoSelectWrapper::readFunctionName() +{ + char functionPrefix = mConstSelect.mSelectRule[FunctionPrefixOffset]; + std::string functionIndex = mConstSelect.mSelectRule.substr(FunctionIndexOffset, 2); + int convertedIndex = -1; + + // Read in function index, form ## from 00 .. 73, skip leading zero + if (functionIndex[0] == '0') + functionIndex = functionIndex[1]; + + std::stringstream stream; + stream << functionIndex; + stream >> convertedIndex; + + switch (functionPrefix) + { + case '1': + if (convertedIndex >= 0 && convertedIndex <= 73) + mFunctionName = static_cast(convertedIndex); + else + mFunctionName = Function_None; + break; + + case '2': mFunctionName = Function_Global; break; + case '3': mFunctionName = Function_Local; break; + case '4': mFunctionName = Function_Journal; break; + case '5': mFunctionName = Function_Item; break; + case '6': mFunctionName = Function_Dead; break; + case '7': mFunctionName = Function_NotId; break; + case '8': mFunctionName = Function_NotFaction; break; + case '9': mFunctionName = Function_NotClass; break; + case 'A': mFunctionName = Function_NotRace; break; + case 'B': mFunctionName = Function_NotCell; break; + case 'C': mFunctionName = Function_NotLocal; break; + default: mFunctionName = Function_None; break; + } +} + +void CSMWorld::ConstInfoSelectWrapper::readRelationType() +{ + char relationIndex = mConstSelect.mSelectRule[RelationIndexOffset]; + + switch (relationIndex) + { + case '0': mRelationType = Relation_Equal; break; + case '1': mRelationType = Relation_NotEqual; break; + case '2': mRelationType = Relation_Greater; break; + case '3': mRelationType = Relation_GreaterOrEqual; break; + case '4': mRelationType = Relation_Less; break; + case '5': mRelationType = Relation_LessOrEqual; break; + default: mRelationType = Relation_None; + } +} + +void CSMWorld::ConstInfoSelectWrapper::readVariableName() +{ + if (mConstSelect.mSelectRule.size() >= VarNameOffset) + mVariableName = mConstSelect.mSelectRule.substr(VarNameOffset); + else + mVariableName.clear(); +} + +void CSMWorld::ConstInfoSelectWrapper::updateHasVariable() +{ + switch (mFunctionName) + { + case Function_Global: + case Function_Local: + case Function_Journal: + case Function_Item: + case Function_Dead: + case Function_NotId: + case Function_NotFaction: + case Function_NotClass: + case Function_NotRace: + case Function_NotCell: + case Function_NotLocal: + mHasVariable = true; + break; + + default: + mHasVariable = false; + break; + } +} + +void CSMWorld::ConstInfoSelectWrapper::updateComparisonType() +{ + switch (mFunctionName) + { + // Boolean + case Function_NotId: + case Function_NotFaction: + case Function_NotClass: + case Function_NotRace: + case Function_NotCell: + case Function_NotLocal: + case Function_PcExpelled: + case Function_PcCommonDisease: + case Function_PcBlightDisease: + case Function_SameSex: + case Function_SameRace: + case Function_SameFaction: + case Function_Detected: + case Function_Alarmed: + case Function_PcCorpus: + case Function_PcVampire: + case Function_Attacked: + case Function_TalkedToPc: + case Function_ShouldAttack: + case Function_Werewolf: + mComparisonType = Comparison_Boolean; + break; + + // Integer + case Function_Journal: + case Function_Item: + case Function_Dead: + case Function_RankLow: + case Function_RankHigh: + case Function_RankRequirement: + case Function_Reputation: + case Function_PcReputation: + case Function_PcLevel: + case Function_PcStrength: + case Function_PcBlock: + case Function_PcArmorer: + case Function_PcMediumArmor: + case Function_PcHeavyArmor: + case Function_PcBluntWeapon: + case Function_PcLongBlade: + case Function_PcAxe: + case Function_PcSpear: + case Function_PcAthletics: + case Function_PcEnchant: + case Function_PcDestruction: + case Function_PcAlteration: + case Function_PcIllusion: + case Function_PcConjuration: + case Function_PcMysticism: + case Function_PcRestoration: + case Function_PcAlchemy: + case Function_PcUnarmored: + case Function_PcSecurity: + case Function_PcSneak: + case Function_PcAcrobatics: + case Function_PcLightArmor: + case Function_PcShortBlade: + case Function_PcMarksman: + case Function_PcMerchantile: + case Function_PcSpeechcraft: + case Function_PcHandToHand: + case Function_PcGender: + case Function_PcClothingModifier: + case Function_PcCrimeLevel: + case Function_FactionRankDifference: + case Function_Choice: + case Function_PcIntelligence: + case Function_PcWillpower: + case Function_PcAgility: + case Function_PcSpeed: + case Function_PcEndurance: + case Function_PcPersonality: + case Function_PcLuck: + case Function_Weather: + case Function_Level: + case Function_CreatureTarget: + case Function_FriendHit: + case Function_Fight: + case Function_Hello: + case Function_Alarm: + case Function_Flee: + case Function_PcWerewolfKills: + mComparisonType = Comparison_Integer; + break; + + // Numeric + case Function_Global: + case Function_Local: + + case Function_Health_Percent: + case Function_PcHealthPercent: + case Function_PcMagicka: + case Function_PcFatigue: + case Function_PcHealth: + mComparisonType = Comparison_Numeric; + break; + + default: + mComparisonType = Comparison_None; + break; + } +} + +std::pair CSMWorld::ConstInfoSelectWrapper::getConditionIntRange() const +{ + const int IntMax = std::numeric_limits::max(); + const int IntMin = std::numeric_limits::min(); + const std::pair InvalidRange(IntMax, IntMin); + + int value = mConstSelect.mValue.getInteger(); + + switch (mRelationType) + { + case Relation_Equal: + case Relation_NotEqual: + return std::pair(value, value); + + case Relation_Greater: + if (value == IntMax) + { + return InvalidRange; + } + else + { + return std::pair(value + 1, IntMax); + } + break; + + case Relation_GreaterOrEqual: + return std::pair(value, IntMax); + + case Relation_Less: + if (value == IntMin) + { + return InvalidRange; + } + else + { + return std::pair(IntMin, value - 1); + } + + case Relation_LessOrEqual: + return std::pair(IntMin, value); + + default: + throw std::logic_error("InfoSelectWrapper: relation does not have a range"); + } +} + +std::pair CSMWorld::ConstInfoSelectWrapper::getConditionFloatRange() const +{ + const float FloatMax = std::numeric_limits::infinity(); + const float FloatMin = -std::numeric_limits::infinity(); + const float Epsilon = std::numeric_limits::epsilon(); + const std::pair InvalidRange(FloatMax, FloatMin); + + float value = mConstSelect.mValue.getFloat(); + + switch (mRelationType) + { + case Relation_Equal: + case Relation_NotEqual: + return std::pair(value, value); + + case Relation_Greater: + return std::pair(value + Epsilon, FloatMax); + + case Relation_GreaterOrEqual: + return std::pair(value, FloatMax); + + case Relation_Less: + return std::pair(FloatMin, value - Epsilon); + + case Relation_LessOrEqual: + return std::pair(FloatMin, value); + + default: + throw std::logic_error("InfoSelectWrapper: given relation does not have a range"); + } +} + +std::pair CSMWorld::ConstInfoSelectWrapper::getValidIntRange() const +{ + const int IntMax = std::numeric_limits::max(); + const int IntMin = std::numeric_limits::min(); + + switch (mFunctionName) + { + // Boolean + case Function_NotId: + case Function_NotFaction: + case Function_NotClass: + case Function_NotRace: + case Function_NotCell: + case Function_NotLocal: + case Function_PcExpelled: + case Function_PcCommonDisease: + case Function_PcBlightDisease: + case Function_SameSex: + case Function_SameRace: + case Function_SameFaction: + case Function_Detected: + case Function_Alarmed: + case Function_PcCorpus: + case Function_PcVampire: + case Function_Attacked: + case Function_TalkedToPc: + case Function_ShouldAttack: + case Function_Werewolf: + return std::pair(0, 1); + + // Integer + case Function_RankLow: + case Function_RankHigh: + case Function_Reputation: + case Function_PcReputation: + case Function_Journal: + return std::pair(IntMin, IntMax); + + case Function_Item: + case Function_Dead: + case Function_PcLevel: + case Function_PcStrength: + case Function_PcBlock: + case Function_PcArmorer: + case Function_PcMediumArmor: + case Function_PcHeavyArmor: + case Function_PcBluntWeapon: + case Function_PcLongBlade: + case Function_PcAxe: + case Function_PcSpear: + case Function_PcAthletics: + case Function_PcEnchant: + case Function_PcDestruction: + case Function_PcAlteration: + case Function_PcIllusion: + case Function_PcConjuration: + case Function_PcMysticism: + case Function_PcRestoration: + case Function_PcAlchemy: + case Function_PcUnarmored: + case Function_PcSecurity: + case Function_PcSneak: + case Function_PcAcrobatics: + case Function_PcLightArmor: + case Function_PcShortBlade: + case Function_PcMarksman: + case Function_PcMerchantile: + case Function_PcSpeechcraft: + case Function_PcHandToHand: + case Function_PcClothingModifier: + case Function_PcCrimeLevel: + case Function_Choice: + case Function_PcIntelligence: + case Function_PcWillpower: + case Function_PcAgility: + case Function_PcSpeed: + case Function_PcEndurance: + case Function_PcPersonality: + case Function_PcLuck: + case Function_Level: + case Function_PcWerewolfKills: + return std::pair(0, IntMax); + + case Function_Fight: + case Function_Hello: + case Function_Alarm: + case Function_Flee: + return std::pair(0, 100); + + case Function_Weather: + return std::pair(0, 9); + + case Function_FriendHit: + return std::pair(0, 4); + + case Function_RankRequirement: + return std::pair(0, 3); + + case Function_CreatureTarget: + return std::pair(0, 2); + + case Function_PcGender: + return std::pair(0, 1); + + case Function_FactionRankDifference: + return std::pair(-9, 9); + + // Numeric + case Function_Global: + case Function_Local: + return std::pair(IntMin, IntMax); + + case Function_PcMagicka: + case Function_PcFatigue: + case Function_PcHealth: + return std::pair(0, IntMax); + + case Function_Health_Percent: + case Function_PcHealthPercent: + return std::pair(0, 100); + + default: + throw std::runtime_error("InfoSelectWrapper: function does not exist"); + } +} + +std::pair CSMWorld::ConstInfoSelectWrapper::getValidFloatRange() const +{ + const float FloatMax = std::numeric_limits::infinity(); + const float FloatMin = -std::numeric_limits::infinity(); + + switch (mFunctionName) + { + // Numeric + case Function_Global: + case Function_Local: + case Function_NotLocal: + return std::pair(FloatMin, FloatMax); + + case Function_PcMagicka: + case Function_PcFatigue: + case Function_PcHealth: + return std::pair(0, FloatMax); + + case Function_Health_Percent: + case Function_PcHealthPercent: + return std::pair(0, 100); + + default: + throw std::runtime_error("InfoSelectWrapper: function does not exist or is not numeric"); + } +} + +template +bool CSMWorld::ConstInfoSelectWrapper::rangeContains(T1 value, std::pair range) const +{ + return (value >= range.first && value <= range.second); +} + +template +bool CSMWorld::ConstInfoSelectWrapper::rangeFullyContains(std::pair containingRange, + std::pair testRange) const +{ + return (containingRange.first <= testRange.first) && (testRange.second <= containingRange.second); +} + +template +bool CSMWorld::ConstInfoSelectWrapper::rangesOverlap(std::pair range1, std::pair range2) const +{ + // One of the bounds of either range should fall within the other range + return + (range1.first <= range2.first && range2.first <= range1.second) || + (range1.first <= range2.second && range2.second <= range1.second) || + (range2.first <= range1.first && range1.first <= range2.second) || + (range2.first <= range1.second && range1.second <= range2.second); +} + +template +bool CSMWorld::ConstInfoSelectWrapper::rangesMatch(std::pair range1, std::pair range2) const +{ + return (range1.first == range2.first && range1.second == range2.second); +} + +template +bool CSMWorld::ConstInfoSelectWrapper::conditionIsAlwaysTrue(std::pair conditionRange, + std::pair validRange) const +{ + switch (mRelationType) + { + case Relation_Equal: + return false; + + case Relation_NotEqual: + // If value is not within range, it will always be true + return !rangeContains(conditionRange.first, validRange); + + case Relation_Greater: + case Relation_GreaterOrEqual: + case Relation_Less: + case Relation_LessOrEqual: + // If the valid range is completely within the condition range, it will always be true + return rangeFullyContains(conditionRange, validRange); + + default: + throw std::logic_error("InfoCondition: operator can not be used to compare"); + } + + return false; +} + +template +bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue(std::pair conditionRange, + std::pair validRange) const +{ + switch (mRelationType) + { + case Relation_Equal: + return !rangeContains(conditionRange.first, validRange); + + case Relation_NotEqual: + return false; + + case Relation_Greater: + case Relation_GreaterOrEqual: + case Relation_Less: + case Relation_LessOrEqual: + // If ranges do not overlap, it will never be true + return !rangesOverlap(conditionRange, validRange); + + default: + throw std::logic_error("InfoCondition: operator can not be used to compare"); + } + + return false; +} + +// InfoSelectWrapper + +CSMWorld::InfoSelectWrapper::InfoSelectWrapper(ESM::DialInfo::SelectStruct& select) + : CSMWorld::ConstInfoSelectWrapper(select), mSelect(select) +{ +} + +void CSMWorld::InfoSelectWrapper::setFunctionName(FunctionName name) +{ + mFunctionName = name; + updateHasVariable(); + updateComparisonType(); +} + +void CSMWorld::InfoSelectWrapper::setRelationType(RelationType type) +{ + mRelationType = type; +} + +void CSMWorld::InfoSelectWrapper::setVariableName(const std::string& name) +{ + mVariableName = name; +} + +void CSMWorld::InfoSelectWrapper::setDefaults() +{ + if (!variantTypeIsValid()) + mSelect.mValue.setType(ESM::VT_Int); + + switch (mComparisonType) + { + case Comparison_Boolean: + setRelationType(Relation_Equal); + mSelect.mValue.setInteger(1); + break; + + case Comparison_Integer: + case Comparison_Numeric: + setRelationType(Relation_Greater); + mSelect.mValue.setInteger(0); + break; + + default: + // Do nothing + break; + } + + update(); +} + +void CSMWorld::InfoSelectWrapper::update() +{ + std::ostringstream stream; + + // Leading 0 + stream << '0'; + + // Write Function + + bool writeIndex = false; + size_t functionIndex = static_cast(mFunctionName); + + switch (mFunctionName) + { + case Function_None: stream << '0'; break; + case Function_Global: stream << '2'; break; + case Function_Local: stream << '3'; break; + case Function_Journal: stream << '4'; break; + case Function_Item: stream << '5'; break; + case Function_Dead: stream << '6'; break; + case Function_NotId: stream << '7'; break; + case Function_NotFaction: stream << '8'; break; + case Function_NotClass: stream << '9'; break; + case Function_NotRace: stream << 'A'; break; + case Function_NotCell: stream << 'B'; break; + case Function_NotLocal: stream << 'C'; break; + default: stream << '1'; writeIndex = true; break; + } + + if (writeIndex && functionIndex < 10) // leading 0 + stream << '0' << functionIndex; + else if (writeIndex) + stream << functionIndex; + else + stream << "00"; + + // Write Relation + switch (mRelationType) + { + case Relation_Equal: stream << '0'; break; + case Relation_NotEqual: stream << '1'; break; + case Relation_Greater: stream << '2'; break; + case Relation_GreaterOrEqual: stream << '3'; break; + case Relation_Less: stream << '4'; break; + case Relation_LessOrEqual: stream << '5'; break; + default: stream << '0'; break; + } + + if (mHasVariable) + stream << mVariableName; + + mSelect.mSelectRule = stream.str(); +} + +ESM::Variant& CSMWorld::InfoSelectWrapper::getVariant() +{ + return mSelect.mValue; +} diff --git a/apps/opencs/model/world/infoselectwrapper.hpp b/apps/opencs/model/world/infoselectwrapper.hpp new file mode 100644 index 0000000000..ce26a46dc7 --- /dev/null +++ b/apps/opencs/model/world/infoselectwrapper.hpp @@ -0,0 +1,243 @@ +#ifndef CSM_WORLD_INFOSELECTWRAPPER_H +#define CSM_WORLD_INFOSELECTWRAPPER_H + +#include + +namespace CSMWorld +{ + // ESM::DialInfo::SelectStruct.mSelectRule + // 012345... + // ^^^ ^^ + // ||| || + // ||| |+------------- condition variable string + // ||| +-------------- comparison type, ['0'..'5']; e.g. !=, <, >=, etc + // ||+---------------- function index (encoded, where function == '1') + // |+----------------- function, ['1'..'C']; e.g. Global, Local, Not ID, etc + // +------------------ unknown + // + + // Wrapper for DialInfo::SelectStruct + class ConstInfoSelectWrapper + { + public: + + // Order matters + enum FunctionName + { + Function_RankLow=0, + Function_RankHigh, + Function_RankRequirement, + Function_Reputation, + Function_Health_Percent, + Function_PcReputation, + Function_PcLevel, + Function_PcHealthPercent, + Function_PcMagicka, + Function_PcFatigue, + Function_PcStrength, + Function_PcBlock, + Function_PcArmorer, + Function_PcMediumArmor, + Function_PcHeavyArmor, + Function_PcBluntWeapon, + Function_PcLongBlade, + Function_PcAxe, + Function_PcSpear, + Function_PcAthletics, + Function_PcEnchant, + Function_PcDestruction, + Function_PcAlteration, + Function_PcIllusion, + Function_PcConjuration, + Function_PcMysticism, + Function_PcRestoration, + Function_PcAlchemy, + Function_PcUnarmored, + Function_PcSecurity, + Function_PcSneak, + Function_PcAcrobatics, + Function_PcLightArmor, + Function_PcShortBlade, + Function_PcMarksman, + Function_PcMerchantile, + Function_PcSpeechcraft, + Function_PcHandToHand, + Function_PcGender, + Function_PcExpelled, + Function_PcCommonDisease, + Function_PcBlightDisease, + Function_PcClothingModifier, + Function_PcCrimeLevel, + Function_SameSex, + Function_SameRace, + Function_SameFaction, + Function_FactionRankDifference, + Function_Detected, + Function_Alarmed, + Function_Choice, + Function_PcIntelligence, + Function_PcWillpower, + Function_PcAgility, + Function_PcSpeed, + Function_PcEndurance, + Function_PcPersonality, + Function_PcLuck, + Function_PcCorpus, + Function_Weather, + Function_PcVampire, + Function_Level, + Function_Attacked, + Function_TalkedToPc, + Function_PcHealth, + Function_CreatureTarget, + Function_FriendHit, + Function_Fight, + Function_Hello, + Function_Alarm, + Function_Flee, + Function_ShouldAttack, + Function_Werewolf, + Function_PcWerewolfKills=73, + + Function_Global, + Function_Local, + Function_Journal, + Function_Item, + Function_Dead, + Function_NotId, + Function_NotFaction, + Function_NotClass, + Function_NotRace, + Function_NotCell, + Function_NotLocal, + + Function_None + }; + + enum RelationType + { + Relation_Equal, + Relation_NotEqual, + Relation_Greater, + Relation_GreaterOrEqual, + Relation_Less, + Relation_LessOrEqual, + + Relation_None + }; + + enum ComparisonType + { + Comparison_Boolean, + Comparison_Integer, + Comparison_Numeric, + + Comparison_None + }; + + static const size_t RuleMinSize; + + static const size_t FunctionPrefixOffset; + static const size_t FunctionIndexOffset; + static const size_t RelationIndexOffset; + static const size_t VarNameOffset; + + static const char* FunctionEnumStrings[]; + static const char* RelationEnumStrings[]; + static const char* ComparisonEnumStrings[]; + + static std::string convertToString(FunctionName name); + static std::string convertToString(RelationType type); + static std::string convertToString(ComparisonType type); + + ConstInfoSelectWrapper(const ESM::DialInfo::SelectStruct& select); + + FunctionName getFunctionName() const; + RelationType getRelationType() const; + ComparisonType getComparisonType() const; + + bool hasVariable() const; + const std::string& getVariableName() const; + + bool conditionIsAlwaysTrue() const; + bool conditionIsNeverTrue() const; + bool variantTypeIsValid() const; + + const ESM::Variant& getVariant() const; + + std::string toString() const; + + protected: + + void readRule(); + void readFunctionName(); + void readRelationType(); + void readVariableName(); + void updateHasVariable(); + void updateComparisonType(); + + std::pair getConditionIntRange() const; + std::pair getConditionFloatRange() const; + + std::pair getValidIntRange() const; + std::pair getValidFloatRange() const; + + template + bool rangeContains(Type1 value, std::pair range) const; + + template + bool rangesOverlap(std::pair range1, std::pair range2) const; + + template + bool rangeFullyContains(std::pair containing, std::pair test) const; + + template + bool rangesMatch(std::pair range1, std::pair range2) const; + + template + bool conditionIsAlwaysTrue(std::pair conditionRange, std::pair validRange) const; + + template + bool conditionIsNeverTrue(std::pair conditionRange, std::pair validRange) const; + + FunctionName mFunctionName; + RelationType mRelationType; + ComparisonType mComparisonType; + + bool mHasVariable; + std::string mVariableName; + + private: + + const ESM::DialInfo::SelectStruct& mConstSelect; + }; + + // Wrapper for DialInfo::SelectStruct that can modify the wrapped select struct + class InfoSelectWrapper : public ConstInfoSelectWrapper + { + public: + + InfoSelectWrapper(ESM::DialInfo::SelectStruct& select); + + // Wrapped SelectStruct will not be modified until update() is called + void setFunctionName(FunctionName name); + void setRelationType(RelationType type); + void setVariableName(const std::string& name); + + // Modified wrapped SelectStruct + void update(); + + // This sets properties based on the function name to its defaults and updates the wrapped object + void setDefaults(); + + ESM::Variant& getVariant(); + + private: + + ESM::DialInfo::SelectStruct& mSelect; + + void writeRule(); + }; +} + +#endif diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index bfacc15dc9..464757cd43 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -6,6 +6,7 @@ #include "idcollection.hpp" #include "pathgrid.hpp" #include "info.hpp" +#include "infoselectwrapper.hpp" namespace CSMWorld { @@ -26,20 +27,8 @@ namespace CSMWorld point.mConnectionNum = 0; point.mUnknown = 0; - // inserting a point should trigger re-indexing of the edges - // - // FIXME: does not auto refresh edges table view - std::vector::iterator iter = pathgrid.mEdges.begin(); - for (;iter != pathgrid.mEdges.end(); ++iter) - { - if ((*iter).mV0 >= position) - (*iter).mV0++; - if ((*iter).mV1 >= position) - (*iter).mV1++; - } - points.insert(points.begin()+position, point); - pathgrid.mData.mS2 += 1; // increment the number of points + pathgrid.mData.mS2 = pathgrid.mPoints.size(); record.setModified (pathgrid); } @@ -53,28 +42,10 @@ namespace CSMWorld if (rowToRemove < 0 || rowToRemove >= static_cast (points.size())) throw std::runtime_error ("index out of range"); - // deleting a point should trigger re-indexing of the edges - // dangling edges are not allowed and hence removed - // - // FIXME: does not auto refresh edges table view - std::vector::iterator iter = pathgrid.mEdges.begin(); - for (; iter != pathgrid.mEdges.end();) - { - if (((*iter).mV0 == rowToRemove) || ((*iter).mV1 == rowToRemove)) - iter = pathgrid.mEdges.erase(iter); - else - { - if ((*iter).mV0 > rowToRemove) - (*iter).mV0--; - - if ((*iter).mV1 > rowToRemove) - (*iter).mV1--; - - ++iter; - } - } + // Do not remove dangling edges, does not work with current undo mechanism + // Do not automatically adjust indices, what would be done with dangling edges? points.erase(points.begin()+rowToRemove); - pathgrid.mData.mS2 -= 1; // decrement the number of points + pathgrid.mData.mS2 = pathgrid.mPoints.size(); record.setModified (pathgrid); } @@ -83,14 +54,8 @@ namespace CSMWorld const NestedTableWrapperBase& nestedTable) const { Pathgrid pathgrid = record.get(); - - pathgrid.mPoints = - static_cast(nestedTable).mRecord.mPoints; - pathgrid.mData.mS2 = - static_cast(nestedTable).mRecord.mData.mS2; - // also update edges in case points were added/removed - pathgrid.mEdges = - static_cast(nestedTable).mRecord.mEdges; + pathgrid.mPoints = static_cast &>(nestedTable).mNestedTable; + pathgrid.mData.mS2 = pathgrid.mPoints.size(); record.setModified (pathgrid); } @@ -98,7 +63,7 @@ namespace CSMWorld NestedTableWrapperBase* PathgridPointListAdapter::table(const Record& record) const { // deleted by dtor of NestedTableStoring - return new PathgridPointsWrap(record.get()); + return new NestedTableWrapper(record.get().mPoints); } QVariant PathgridPointListAdapter::getData(const Record& record, @@ -146,7 +111,6 @@ namespace CSMWorld PathgridEdgeListAdapter::PathgridEdgeListAdapter () {} - // ToDo: seems to be auto-sorted in the dialog table display after insertion void PathgridEdgeListAdapter::addRow(Record& record, int position) const { Pathgrid pathgrid = record.get(); @@ -217,7 +181,6 @@ namespace CSMWorld } } - // ToDo: detect duplicates in mEdges void PathgridEdgeListAdapter::setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const { @@ -277,7 +240,7 @@ namespace CSMWorld // WARNING: Assumed that the table view has the same order as std::map std::map::iterator iter = reactions.begin(); for(int i = 0; i < rowToRemove; ++i) - iter++; + ++iter; reactions.erase(iter); record.setModified (faction); @@ -314,7 +277,7 @@ namespace CSMWorld // WARNING: Assumed that the table view has the same order as std::map std::map::const_iterator iter = reactions.begin(); for(int i = 0; i < subRowIndex; ++i) - iter++; + ++iter; switch (subColIndex) { case 0: return QString((*iter).first.c_str()); @@ -337,7 +300,7 @@ namespace CSMWorld // WARNING: Assumed that the table view has the same order as std::map std::map::iterator iter = reactions.begin(); for(int i = 0; i < subRowIndex; ++i) - iter++; + ++iter; std::string factionId = (*iter).first; int reaction = (*iter).second; @@ -529,16 +492,6 @@ namespace CSMWorld return 1; // fixed at size 1 } - // ESM::DialInfo::SelectStruct.mSelectRule - // 012345... - // ^^^ ^^ - // ||| || - // ||| |+------------- condition variable string - // ||| +-------------- comparison type, ['0'..'5']; e.g. !=, <, >=, etc - // ||+---------------- function index (encoded, where function == '1') - // |+----------------- function, ['1'..'C']; e.g. Global, Local, Not ID, etc - // +------------------ unknown - // InfoConditionAdapter::InfoConditionAdapter () {} void InfoConditionAdapter::addRow(Record& record, int position) const @@ -547,11 +500,11 @@ namespace CSMWorld std::vector& conditions = info.mSelects; - // blank row + // default row ESM::DialInfo::SelectStruct condStruct; - condStruct.mSelectRule = "00000"; + condStruct.mSelectRule = "01000"; condStruct.mValue = ESM::Variant(); - condStruct.mValue.setType(ESM::VT_Int); // default to ints + condStruct.mValue.setType(ESM::VT_Int); conditions.insert(conditions.begin()+position, condStruct); @@ -589,89 +542,6 @@ namespace CSMWorld return new NestedTableWrapper >(record.get().mSelects); } - // See the mappings in MWDialogue::SelectWrapper::getArgument - // from ESM::Attribute, ESM::Skill and MWMechanics::CreatureStats (for AI) - static std::map populateEncToInfoFunc() - { - std::map funcMap; - funcMap["00"] = "Rank Low"; - funcMap["01"] = "Rank High"; - funcMap["02"] = "Rank Requirement"; - funcMap["03"] = "Reputation"; - funcMap["04"] = "Health Percent"; - funcMap["05"] = "PC Reputation"; - funcMap["06"] = "PC Level"; - funcMap["07"] = "PC Health Percent"; - funcMap["08"] = "PC Magicka"; - funcMap["09"] = "PC Fatigue"; - funcMap["10"] = "PC Strength"; - funcMap["11"] = "PC Block"; - funcMap["12"] = "PC Armorer"; - funcMap["13"] = "PC Medium Armor"; - funcMap["14"] = "PC Heavy Armor"; - funcMap["15"] = "PC Blunt Weapon"; - funcMap["16"] = "PC Long Blade"; - funcMap["17"] = "PC Axe"; - funcMap["18"] = "PC Spear"; - funcMap["19"] = "PC Athletics"; - funcMap["20"] = "PC Enchant"; - funcMap["21"] = "PC Destruction"; - funcMap["22"] = "PC Alteration"; - funcMap["23"] = "PC Illusion"; - funcMap["24"] = "PC Conjuration"; - funcMap["25"] = "PC Mysticism"; - funcMap["26"] = "PC Restoration"; - funcMap["27"] = "PC Alchemy"; - funcMap["28"] = "PC Unarmored"; - funcMap["29"] = "PC Security"; - funcMap["30"] = "PC Sneak"; - funcMap["31"] = "PC Acrobatics"; - funcMap["32"] = "PC Light Armor"; - funcMap["33"] = "PC Short Blade"; - funcMap["34"] = "PC Marksman"; - funcMap["35"] = "PC Merchantile"; - funcMap["36"] = "PC Speechcraft"; - funcMap["37"] = "PC Hand To Hand"; - funcMap["38"] = "PC Sex"; - funcMap["39"] = "PC Expelled"; - funcMap["40"] = "PC Common Disease"; - funcMap["41"] = "PC Blight Disease"; - funcMap["42"] = "PC Clothing Modifier"; - funcMap["43"] = "PC Crime Level"; - funcMap["44"] = "Same Sex"; - funcMap["45"] = "Same Race"; - funcMap["46"] = "Same Faction"; - funcMap["47"] = "Faction Rank Difference"; - funcMap["48"] = "Detected"; - funcMap["49"] = "Alarmed"; - funcMap["50"] = "Choice"; - funcMap["51"] = "PC Intelligence"; - funcMap["52"] = "PC Willpower"; - funcMap["53"] = "PC Agility"; - funcMap["54"] = "PC Speed"; - funcMap["55"] = "PC Endurance"; - funcMap["56"] = "PC Personality"; - funcMap["57"] = "PC Luck"; - funcMap["58"] = "PC Corpus"; - funcMap["59"] = "Weather"; - funcMap["60"] = "PC Vampire"; - funcMap["61"] = "Level"; - funcMap["62"] = "Attacked"; - funcMap["63"] = "Talked To PC"; - funcMap["64"] = "PC Health"; - funcMap["65"] = "Creature Target"; - funcMap["66"] = "Friend Hit"; - funcMap["67"] = "Fight"; - funcMap["68"] = "Hello"; - funcMap["69"] = "Alarm"; - funcMap["70"] = "Flee"; - funcMap["71"] = "Should Attack"; - funcMap["72"] = "Werewolf"; - funcMap["73"] = "PC Werewolf Kills"; - return funcMap; - } - static const std::map sEncToInfoFunc = populateEncToInfoFunc(); - QVariant InfoConditionAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { @@ -682,70 +552,36 @@ namespace CSMWorld if (subRowIndex < 0 || subRowIndex >= static_cast (conditions.size())) throw std::runtime_error ("index out of range"); + ConstInfoSelectWrapper infoSelectWrapper(conditions[subRowIndex]); + switch (subColIndex) { case 0: { - char condType = conditions[subRowIndex].mSelectRule[1]; - switch (condType) - { - case '0': return 0; // blank space - case '1': return 1; // Function - case '2': return 2; // Global - case '3': return 3; // Local - case '4': return 4; // Journal - case '5': return 5; // Item - case '6': return 6; // Dead - case '7': return 7; // Not ID - case '8': return 8; // Not Factio - case '9': return 9; // Not Class - case 'A': return 10; // Not Race - case 'B': return 11; // Not Cell - case 'C': return 12; // Not Local - default: return QVariant(); // TODO: log an error? - } + return infoSelectWrapper.getFunctionName(); } case 1: { - if (conditions[subRowIndex].mSelectRule[1] == '1') - { - // throws an exception if the encoding is not found - return sEncToInfoFunc.at(conditions[subRowIndex].mSelectRule.substr(2, 2)).c_str(); - } + if (infoSelectWrapper.hasVariable()) + return QString(infoSelectWrapper.getVariableName().c_str()); else - return QString(conditions[subRowIndex].mSelectRule.substr(5).c_str()); + return ""; } case 2: { - char compType = conditions[subRowIndex].mSelectRule[4]; - switch (compType) - { - case '0': return 3; // = - case '1': return 0; // != - case '2': return 4; // > - case '3': return 5; // >= - case '4': return 1; // < - case '5': return 2; // <= - default: return QVariant(); // TODO: log an error? - } + return infoSelectWrapper.getRelationType(); } case 3: { - switch (conditions[subRowIndex].mValue.getType()) + switch (infoSelectWrapper.getVariant().getType()) { - case ESM::VT_String: - { - return QString::fromUtf8 (conditions[subRowIndex].mValue.getString().c_str()); - } case ESM::VT_Int: - case ESM::VT_Short: - case ESM::VT_Long: { - return conditions[subRowIndex].mValue.getInteger(); + return infoSelectWrapper.getVariant().getInteger(); } case ESM::VT_Float: { - return conditions[subRowIndex].mValue.getFloat(); + return infoSelectWrapper.getVariant().getFloat(); } default: return QVariant(); } @@ -764,101 +600,63 @@ namespace CSMWorld if (subRowIndex < 0 || subRowIndex >= static_cast (conditions.size())) throw std::runtime_error ("index out of range"); + InfoSelectWrapper infoSelectWrapper(conditions[subRowIndex]); + bool conversionResult = false; + switch (subColIndex) { - case 0: + case 0: // Function { - // See sInfoCondFunc in columns.cpp for the enum values - switch (value.toInt()) - { - // FIXME: when these change the values of the other columns need to change - // correspondingly (and automatically) - case 1: - { - conditions[subRowIndex].mSelectRule[1] = '1'; // Function - // default to "Rank Low" - conditions[subRowIndex].mSelectRule[2] = '0'; - conditions[subRowIndex].mSelectRule[3] = '0'; - break; - } - case 2: conditions[subRowIndex].mSelectRule[1] = '2'; break; // Global - case 3: conditions[subRowIndex].mSelectRule[1] = '3'; break; // Local - case 4: conditions[subRowIndex].mSelectRule[1] = '4'; break; // Journal - case 5: conditions[subRowIndex].mSelectRule[1] = '5'; break; // Item - case 6: conditions[subRowIndex].mSelectRule[1] = '6'; break; // Dead - case 7: conditions[subRowIndex].mSelectRule[1] = '7'; break; // Not ID - case 8: conditions[subRowIndex].mSelectRule[1] = '8'; break; // Not Faction - case 9: conditions[subRowIndex].mSelectRule[1] = '9'; break; // Not Class - case 10: conditions[subRowIndex].mSelectRule[1] = 'A'; break; // Not Race - case 11: conditions[subRowIndex].mSelectRule[1] = 'B'; break; // Not Cell - case 12: conditions[subRowIndex].mSelectRule[1] = 'C'; break; // Not Local - default: return; // return without saving - } - break; - } - case 1: - { - if (conditions[subRowIndex].mSelectRule[1] == '1') - { - std::map::const_iterator it = sEncToInfoFunc.begin(); - for (;it != sEncToInfoFunc.end(); ++it) - { - if (it->second == value.toString().toUtf8().constData()) - { - std::string rule = conditions[subRowIndex].mSelectRule.substr(0, 2); - rule.append(it->first); - // leave old values for undo (NOTE: may not be vanilla's behaviour) - rule.append(conditions[subRowIndex].mSelectRule.substr(4)); - conditions[subRowIndex].mSelectRule = rule; - break; - } - } + infoSelectWrapper.setFunctionName(static_cast(value.toInt())); - if (it == sEncToInfoFunc.end()) - return; // return without saving; TODO: maybe log an error here - } - else + if (infoSelectWrapper.getComparisonType() != ConstInfoSelectWrapper::Comparison_Numeric && + infoSelectWrapper.getVariant().getType() != ESM::VT_Int) { - // FIXME: validate the string values before saving, based on the current function - std::string rule = conditions[subRowIndex].mSelectRule.substr(0, 5); - conditions[subRowIndex].mSelectRule = rule.append(value.toString().toUtf8().constData()); + infoSelectWrapper.getVariant().setType(ESM::VT_Int); } + + infoSelectWrapper.update(); break; } - case 2: + case 1: // Variable { - // See sInfoCondComp in columns.cpp for the enum values - switch (value.toInt()) - { - case 0: conditions[subRowIndex].mSelectRule[4] = '1'; break; // != - case 1: conditions[subRowIndex].mSelectRule[4] = '4'; break; // < - case 2: conditions[subRowIndex].mSelectRule[4] = '5'; break; // <= - case 3: conditions[subRowIndex].mSelectRule[4] = '0'; break; // = - case 4: conditions[subRowIndex].mSelectRule[4] = '2'; break; // > - case 5: conditions[subRowIndex].mSelectRule[4] = '3'; break; // >= - default: return; // return without saving - } + infoSelectWrapper.setVariableName(value.toString().toUtf8().constData()); + infoSelectWrapper.update(); break; } - case 3: + case 2: // Relation { - switch (conditions[subRowIndex].mValue.getType()) + infoSelectWrapper.setRelationType(static_cast(value.toInt())); + infoSelectWrapper.update(); + break; + } + case 3: // Value + { + switch (infoSelectWrapper.getComparisonType()) { - case ESM::VT_String: + case ConstInfoSelectWrapper::Comparison_Numeric: { - conditions[subRowIndex].mValue.setString (value.toString().toUtf8().constData()); + // QVariant seems to have issues converting 0 + if ((value.toInt(&conversionResult) && conversionResult) || value.toString().compare("0") == 0) + { + infoSelectWrapper.getVariant().setType(ESM::VT_Int); + infoSelectWrapper.getVariant().setInteger(value.toInt()); + } + else if (value.toFloat(&conversionResult) && conversionResult) + { + infoSelectWrapper.getVariant().setType(ESM::VT_Float); + infoSelectWrapper.getVariant().setFloat(value.toFloat()); + } break; } - case ESM::VT_Int: - case ESM::VT_Short: - case ESM::VT_Long: + case ConstInfoSelectWrapper::Comparison_Boolean: + case ConstInfoSelectWrapper::Comparison_Integer: { - conditions[subRowIndex].mValue.setInteger (value.toInt()); - break; - } - case ESM::VT_Float: - { - conditions[subRowIndex].mValue.setFloat (value.toFloat()); + if ((value.toInt(&conversionResult) && conversionResult) || value.toString().compare("0") == 0) + { + infoSelectWrapper.getVariant().setType(ESM::VT_Int); + infoSelectWrapper.getVariant().setInteger(value.toInt()); + } break; } default: break; @@ -1079,7 +877,7 @@ namespace CSMWorld cell.mAmbi.mFogDensity : QVariant(QVariant::UserType); case 5: { - if (isInterior && !behaveLikeExterior && interiorWater) + if (isInterior && interiorWater) return cell.mWater; else return QVariant(QVariant::UserType); @@ -1145,7 +943,7 @@ namespace CSMWorld } case 5: { - if (isInterior && !behaveLikeExterior && interiorWater) + if (isInterior && interiorWater) cell.mWater = value.toFloat(); else return; // return without saving @@ -1191,4 +989,105 @@ namespace CSMWorld { return 1; // fixed at size 1 } + + RegionWeatherAdapter::RegionWeatherAdapter () {} + + void RegionWeatherAdapter::addRow(Record& record, int position) const + { + throw std::logic_error ("cannot add a row to a fixed table"); + } + + void RegionWeatherAdapter::removeRow(Record& record, int rowToRemove) const + { + throw std::logic_error ("cannot remove a row from a fixed table"); + } + + void RegionWeatherAdapter::setTable(Record& record, const NestedTableWrapperBase& nestedTable) const + { + throw std::logic_error ("table operation not supported"); + } + + NestedTableWrapperBase* RegionWeatherAdapter::table(const Record& record) const + { + throw std::logic_error ("table operation not supported"); + } + + QVariant RegionWeatherAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const + { + const char* WeatherNames[] = { + "Clear", + "Cloudy", + "Fog", + "Overcast", + "Rain", + "Thunder", + "Ash", + "Blight", + "Snow", + "Blizzard" + }; + + const ESM::Region& region = record.get(); + + if (subColIndex == 0 && subRowIndex >= 0 && subRowIndex < 10) + { + return WeatherNames[subRowIndex]; + } + else if (subColIndex == 1) + { + switch (subRowIndex) + { + case 0: return region.mData.mClear; + case 1: return region.mData.mCloudy; + case 2: return region.mData.mFoggy; + case 3: return region.mData.mOvercast; + case 4: return region.mData.mRain; + case 5: return region.mData.mThunder; + case 6: return region.mData.mAsh; + case 7: return region.mData.mBlight; + case 8: return region.mData.mA; // Snow + case 9: return region.mData.mB; // Blizzard + default: break; + } + } + + throw std::runtime_error("index out of range"); + } + + void RegionWeatherAdapter::setData(Record& record, const QVariant& value, int subRowIndex, + int subColIndex) const + { + ESM::Region region = record.get(); + unsigned char chance = static_cast(value.toInt()); + + if (subColIndex == 1) + { + switch (subRowIndex) + { + case 0: region.mData.mClear = chance; break; + case 1: region.mData.mCloudy = chance; break; + case 2: region.mData.mFoggy = chance; break; + case 3: region.mData.mOvercast = chance; break; + case 4: region.mData.mRain = chance; break; + case 5: region.mData.mThunder = chance; break; + case 6: region.mData.mAsh = chance; break; + case 7: region.mData.mBlight = chance; break; + case 8: region.mData.mA = chance; break; + case 9: region.mData.mB = chance; break; + default: throw std::runtime_error("index out of range"); + } + + record.setModified (region); + } + } + + int RegionWeatherAdapter::getColumnsCount(const Record& record) const + { + return 2; + } + + int RegionWeatherAdapter::getRowsCount(const Record& record) const + { + return 10; + } } diff --git a/apps/opencs/model/world/nestedcoladapterimp.hpp b/apps/opencs/model/world/nestedcoladapterimp.hpp index 2fd569bd0d..131f547a5c 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.hpp +++ b/apps/opencs/model/world/nestedcoladapterimp.hpp @@ -25,21 +25,6 @@ namespace CSMWorld struct Pathgrid; struct Info; - struct PathgridPointsWrap : public NestedTableWrapperBase - { - ESM::Pathgrid mRecord; - - PathgridPointsWrap(ESM::Pathgrid pathgrid) - : mRecord(pathgrid) {} - - virtual ~PathgridPointsWrap() {} - - virtual int size() const - { - return mRecord.mPoints.size(); // used in IdTree::setNestedTable() - } - }; - class PathgridPointListAdapter : public NestedColumnAdapter { public: @@ -540,6 +525,31 @@ namespace CSMWorld virtual int getRowsCount(const Record& record) const; }; + + class RegionWeatherAdapter : public NestedColumnAdapter + { + public: + RegionWeatherAdapter (); + + virtual void addRow(Record& record, int position) const; + + virtual void removeRow(Record& record, int rowToRemove) const; + + virtual void setTable(Record& record, + const NestedTableWrapperBase& nestedTable) const; + + virtual NestedTableWrapperBase* table(const Record& record) const; + + virtual QVariant getData(const Record& record, + int subRowIndex, int subColIndex) const; + + virtual void setData(Record& record, + const QVariant& value, int subRowIndex, int subColIndex) const; + + virtual int getColumnsCount(const Record& record) const; + + virtual int getRowsCount(const Record& record) const; + }; } #endif // CSM_WOLRD_NESTEDCOLADAPTERIMP_H diff --git a/apps/opencs/model/world/ref.cpp b/apps/opencs/model/world/ref.cpp index 638f7ec9ca..0439b94483 100644 --- a/apps/opencs/model/world/ref.cpp +++ b/apps/opencs/model/world/ref.cpp @@ -2,7 +2,11 @@ #include -CSMWorld::CellRef::CellRef() +#include + +#include "cellcoordinates.hpp" + +CSMWorld::CellRef::CellRef() : mNew (true) { mRefNum.mIndex = 0; mRefNum.mContentFile = 0; @@ -10,8 +14,5 @@ CSMWorld::CellRef::CellRef() std::pair CSMWorld::CellRef::getCellIndex() const { - const int cellSize = 8192; - - return std::make_pair ( - std::floor (mPos.pos[0]/cellSize), std::floor (mPos.pos[1]/cellSize)); + return CellCoordinates::coordinatesToCellIndex (mPos.pos[0], mPos.pos[1]); } diff --git a/apps/opencs/model/world/ref.hpp b/apps/opencs/model/world/ref.hpp index c60392221a..5d10a3a1b3 100644 --- a/apps/opencs/model/world/ref.hpp +++ b/apps/opencs/model/world/ref.hpp @@ -13,6 +13,7 @@ namespace CSMWorld std::string mId; std::string mCell; std::string mOriginalCell; + bool mNew; // new reference, not counted yet, ref num not assigned yet CellRef(); diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index 65251a81db..506f9027e4 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -19,6 +19,7 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool Cell& cell2 = base ? cell.mBase : cell.mModified; CellRef ref; + ref.mNew = false; ESM::MovedCellRef mref; bool isDeleted = false; diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index 90a710fc89..5f48b4315e 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -108,7 +108,7 @@ void CSMWorld::IngredEffectRefIdAdapter::setNestedTable (const RefIdColumn* colu ESM::Ingredient ingredient = record.get(); ingredient.mData = - static_cast >&>(nestedTable).mNestedTable.at(0); + static_cast >&>(nestedTable).mNestedTable.at(0); record.setModified (ingredient); } @@ -120,11 +120,11 @@ CSMWorld::NestedTableWrapperBase* CSMWorld::IngredEffectRefIdAdapter::nestedTabl static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); // return the whole struct - std::vector wrap; + std::vector wrap; wrap.push_back(record.get().mData); // deleted by dtor of NestedTableStoring - return new NestedTableWrapper >(wrap); + return new NestedTableWrapper >(wrap); } QVariant CSMWorld::IngredEffectRefIdAdapter::getNestedData (const RefIdColumn *column, @@ -1129,7 +1129,7 @@ void CSMWorld::CreatureAttributesRefIdAdapter::setNestedTable (const RefIdColumn // store the whole struct creature.mData = - static_cast > &>(nestedTable).mNestedTable.at(0); + static_cast > &>(nestedTable).mNestedTable.at(0); record.setModified (creature); } @@ -1141,10 +1141,10 @@ CSMWorld::NestedTableWrapperBase* CSMWorld::CreatureAttributesRefIdAdapter::nest static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); // return the whole struct - std::vector wrap; + std::vector wrap; wrap.push_back(record.get().mData); // deleted by dtor of NestedTableStoring - return new NestedTableWrapper >(wrap); + return new NestedTableWrapper >(wrap); } QVariant CSMWorld::CreatureAttributesRefIdAdapter::getNestedData (const RefIdColumn *column, @@ -1153,7 +1153,7 @@ QVariant CSMWorld::CreatureAttributesRefIdAdapter::getNestedData (const RefIdCol const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); - const ESM::Creature creature = record.get(); + const ESM::Creature& creature = record.get(); if (subColIndex == 0) return subRowIndex; @@ -1235,7 +1235,7 @@ void CSMWorld::CreatureAttackRefIdAdapter::setNestedTable (const RefIdColumn* co // store the whole struct creature.mData = - static_cast > &>(nestedTable).mNestedTable.at(0); + static_cast > &>(nestedTable).mNestedTable.at(0); record.setModified (creature); } @@ -1247,10 +1247,10 @@ CSMWorld::NestedTableWrapperBase* CSMWorld::CreatureAttackRefIdAdapter::nestedTa static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); // return the whole struct - std::vector wrap; + std::vector wrap; wrap.push_back(record.get().mData); // deleted by dtor of NestedTableStoring - return new NestedTableWrapper >(wrap); + return new NestedTableWrapper >(wrap); } QVariant CSMWorld::CreatureAttackRefIdAdapter::getNestedData (const RefIdColumn *column, @@ -1259,7 +1259,7 @@ QVariant CSMWorld::CreatureAttackRefIdAdapter::getNestedData (const RefIdColumn const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); - const ESM::Creature creature = record.get(); + const ESM::Creature& creature = record.get(); if (subRowIndex < 0 || subRowIndex > 2 || subColIndex < 0 || subColIndex > 2) throw std::runtime_error ("index out of range"); @@ -1337,7 +1337,7 @@ QVariant CSMWorld::CreatureMiscRefIdAdapter::getNestedData (const RefIdColumn *c const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); - const ESM::Creature creature = record.get(); + const ESM::Creature& creature = record.get(); switch (subColIndex) { @@ -1440,26 +1440,28 @@ void CSMWorld::WeaponRefIdAdapter::setData (const RefIdColumn *column, RefIdData Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Weapon))); + ESM::Weapon weapon = record.get(); + if (column==mColumns.mType) - record.get().mData.mType = value.toInt(); + weapon.mData.mType = value.toInt(); else if (column==mColumns.mHealth) - record.get().mData.mHealth = value.toInt(); + weapon.mData.mHealth = value.toInt(); else if (column==mColumns.mSpeed) - record.get().mData.mSpeed = value.toFloat(); + weapon.mData.mSpeed = value.toFloat(); else if (column==mColumns.mReach) - record.get().mData.mReach = value.toFloat(); + weapon.mData.mReach = value.toFloat(); else if (column==mColumns.mChop[0]) - record.get().mData.mChop[0] = value.toInt(); + weapon.mData.mChop[0] = value.toInt(); else if (column==mColumns.mChop[1]) - record.get().mData.mChop[1] = value.toInt(); + weapon.mData.mChop[1] = value.toInt(); else if (column==mColumns.mSlash[0]) - record.get().mData.mSlash[0] = value.toInt(); + weapon.mData.mSlash[0] = value.toInt(); else if (column==mColumns.mSlash[1]) - record.get().mData.mSlash[1] = value.toInt(); + weapon.mData.mSlash[1] = value.toInt(); else if (column==mColumns.mThrust[0]) - record.get().mData.mThrust[0] = value.toInt(); + weapon.mData.mThrust[0] = value.toInt(); else if (column==mColumns.mThrust[1]) - record.get().mData.mThrust[1] = value.toInt(); + weapon.mData.mThrust[1] = value.toInt(); else { std::map::const_iterator iter = @@ -1468,11 +1470,16 @@ void CSMWorld::WeaponRefIdAdapter::setData (const RefIdColumn *column, RefIdData if (iter!=mColumns.mFlags.end()) { if (value.toInt()!=0) - record.get().mData.mFlags |= iter->second; + weapon.mData.mFlags |= iter->second; else - record.get().mData.mFlags &= ~iter->second; + weapon.mData.mFlags &= ~iter->second; } else + { EnchantableRefIdAdapter::setData (column, data, index, value); + return; // Don't overwrite changes made by base class + } } + + record.setModified(weapon); } diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index eff7167def..e828a7f4ef 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -1187,7 +1187,7 @@ namespace CSMWorld std::vector& list = container.mInventory.mList; - ESM::ContItem newRow = {0, {""}}; + ESM::ContItem newRow = ESM::ContItem(); if (position >= (int)list.size()) list.push_back(newRow); diff --git a/apps/opencs/model/world/tablemimedata.hpp b/apps/opencs/model/world/tablemimedata.hpp index a42e975618..3be8054bdb 100644 --- a/apps/opencs/model/world/tablemimedata.hpp +++ b/apps/opencs/model/world/tablemimedata.hpp @@ -21,7 +21,7 @@ namespace CSMWorld /// /// This class provides way to construct mimedata object holding the universalid copy /// Universalid is used in the majority of the tables to store type, id, argument types. -/// This way universalid grants a way to retrive record from the concrete table. +/// This way universalid grants a way to retrieve record from the concrete table. /// Please note, that tablemimedata object can hold multiple universalIds in the vector. class TableMimeData : public QMimeData diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp index 6488365652..ca6145b9c6 100644 --- a/apps/opencs/view/doc/filedialog.hpp +++ b/apps/opencs/view/doc/filedialog.hpp @@ -14,9 +14,6 @@ Q_DECLARE_METATYPE (boost::filesystem::path) #include "ui_filedialog.h" -class DataFilesModel; -class PluginsProxyModel; - namespace ContentSelectorView { class ContentSelector; diff --git a/apps/opencs/view/doc/subview.cpp b/apps/opencs/view/doc/subview.cpp index 67a8f8c705..82cbe835e7 100644 --- a/apps/opencs/view/doc/subview.cpp +++ b/apps/opencs/view/doc/subview.cpp @@ -47,7 +47,7 @@ void CSVDoc::SubView::setUniversalId (const CSMWorld::UniversalId& id) void CSVDoc::SubView::closeEvent (QCloseEvent *event) { - emit updateSubViewIndicies (this); + emit updateSubViewIndices (this); } std::string CSVDoc::SubView::getTitle() const diff --git a/apps/opencs/view/doc/subview.hpp b/apps/opencs/view/doc/subview.hpp index 1c5f8a7866..8402bf79a6 100644 --- a/apps/opencs/view/doc/subview.hpp +++ b/apps/opencs/view/doc/subview.hpp @@ -64,7 +64,7 @@ namespace CSVDoc void updateTitle(); - void updateSubViewIndicies (SubView *view = 0); + void updateSubViewIndices (SubView *view = NULL); void universalIdChanged (const CSMWorld::UniversalId& universalId); diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 3491c9de2b..6f14e5a4df 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -343,7 +343,7 @@ void CSVDoc::View::updateTitle() setWindowTitle (QString::fromUtf8(stream.str().c_str())); } -void CSVDoc::View::updateSubViewIndicies(SubView *view) +void CSVDoc::View::updateSubViewIndices(SubView *view) { CSMPrefs::Category& windows = CSMPrefs::State::get()["Windows"]; @@ -373,7 +373,7 @@ void CSVDoc::View::updateSubViewIndicies(SubView *view) else { delete subView->titleBarWidget(); - subView->setTitleBarWidget (0); + subView->setTitleBarWidget (NULL); } } } @@ -402,7 +402,7 @@ void CSVDoc::View::updateActions() CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int totalViews) : mViewManager (viewManager), mDocument (document), mViewIndex (totalViews-1), - mViewTotal (totalViews), mScroll(0), mScrollbarOnly(false) + mViewTotal (totalViews), mScroll(NULL), mScrollbarOnly(false) { CSMPrefs::Category& windows = CSMPrefs::State::get()["Windows"]; @@ -419,10 +419,7 @@ CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int to } else { - mScroll = new QScrollArea(this); - mScroll->setWidgetResizable(true); - mScroll->setWidget(&mSubViewWindow); - setCentralWidget(mScroll); + createScrollArea(); } mOperations = new Operations; @@ -570,36 +567,11 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::strin // mScrollbarOnly = windows["mainwindow-scrollbar"].toString() == "Scrollbar Only"; - QDesktopWidget *dw = QApplication::desktop(); - QRect rect; - if (windows["grow-limit"].isTrue()) - rect = dw->screenGeometry(this); - else - rect = dw->screenGeometry(dw->screen(dw->screenNumber(this))); - - if (!mScrollbarOnly && mScroll && mSubViews.size() > 1) - { - int newWidth = width()+minWidth; - int frameWidth = frameGeometry().width() - width(); - if (newWidth+frameWidth <= rect.width()) - { - resize(newWidth, height()); - // WARNING: below code assumes that new subviews are added to the right - if (x() > rect.width()-(newWidth+frameWidth)) - move(rect.width()-(newWidth+frameWidth), y()); // shift left to stay within the screen - } - else - { - // full width - resize(rect.width()-frameWidth, height()); - mSubViewWindow.setMinimumWidth(mSubViewWindow.width()+minWidth); - move(0, y()); - } - } + updateWidth(windows["grow-limit"].isTrue(), minWidth); mSubViewWindow.addDockWidget (Qt::TopDockWidgetArea, view); - updateSubViewIndicies(); + updateSubViewIndices(); connect (view, SIGNAL (focusId (const CSMWorld::UniversalId&, const std::string&)), this, SLOT (addSubView (const CSMWorld::UniversalId&, const std::string&))); @@ -608,8 +580,8 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::strin connect (view, SIGNAL (updateTitle()), this, SLOT (updateTitle())); - connect (view, SIGNAL (updateSubViewIndicies (SubView *)), - this, SLOT (updateSubViewIndicies (SubView *))); + connect (view, SIGNAL (updateSubViewIndices (SubView *)), + this, SLOT (updateSubViewIndices (SubView *))); view->show(); @@ -631,7 +603,7 @@ void CSVDoc::View::moveScrollBarToEnd(int min, int max) void CSVDoc::View::settingChanged (const CSMPrefs::Setting *setting) { if (*setting=="Windows/hide-subview") - updateSubViewIndicies (0); + updateSubViewIndices (NULL); else if (*setting=="Windows/mainwindow-scrollbar") { if (setting->toString()!="Grow Only") @@ -651,10 +623,7 @@ void CSVDoc::View::settingChanged (const CSMPrefs::Setting *setting) } else { - mScroll = new QScrollArea(this); - mScroll->setWidgetResizable(true); - mScroll->setWidget(&mSubViewWindow); - setCentralWidget(mScroll); + createScrollArea(); } } else if (mScroll) @@ -662,7 +631,7 @@ void CSVDoc::View::settingChanged (const CSMPrefs::Setting *setting) mScroll->takeWidget(); setCentralWidget (&mSubViewWindow); mScroll->deleteLater(); - mScroll = 0; + mScroll = NULL; } } } @@ -959,3 +928,41 @@ void CSVDoc::View::merge() { emit mergeDocument (mDocument); } + +void CSVDoc::View::updateWidth(bool isGrowLimit, int minSubViewWidth) +{ + QDesktopWidget *dw = QApplication::desktop(); + QRect rect; + if (isGrowLimit) + rect = dw->screenGeometry(this); + else + rect = dw->screenGeometry(dw->screen(dw->screenNumber(this))); + + if (!mScrollbarOnly && mScroll && mSubViews.size() > 1) + { + int newWidth = width()+minSubViewWidth; + int frameWidth = frameGeometry().width() - width(); + if (newWidth+frameWidth <= rect.width()) + { + resize(newWidth, height()); + // WARNING: below code assumes that new subviews are added to the right + if (x() > rect.width()-(newWidth+frameWidth)) + move(rect.width()-(newWidth+frameWidth), y()); // shift left to stay within the screen + } + else + { + // full width + resize(rect.width()-frameWidth, height()); + mSubViewWindow.setMinimumWidth(mSubViewWindow.width()+minSubViewWidth); + move(0, y()); + } + } +} + +void CSVDoc::View::createScrollArea() +{ + mScroll = new QScrollArea(this); + mScroll->setWidgetResizable(true); + mScroll->setWidget(&mSubViewWindow); + setCentralWidget(mScroll); +} diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp index 7d53042693..d95499191e 100644 --- a/apps/opencs/view/doc/view.hpp +++ b/apps/opencs/view/doc/view.hpp @@ -95,7 +95,8 @@ namespace CSVDoc void resizeViewHeight (int height); void updateScrollbar(); - + void updateWidth(bool isGrowLimit, int minSubViewWidth); + void createScrollArea(); public: View (ViewManager& viewManager, CSMDoc::Document *document, int totalViews); @@ -143,7 +144,7 @@ namespace CSVDoc void updateTitle(); // called when subviews are added or removed - void updateSubViewIndicies (SubView *view = 0); + void updateSubViewIndices (SubView *view = NULL); private slots: diff --git a/apps/opencs/view/render/cameracontroller.cpp b/apps/opencs/view/render/cameracontroller.cpp new file mode 100644 index 0000000000..dc356827a8 --- /dev/null +++ b/apps/opencs/view/render/cameracontroller.cpp @@ -0,0 +1,650 @@ +#include "cameracontroller.hpp" + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace CSVRender +{ + + /* + Camera Controller + */ + + const osg::Vec3d CameraController::WorldUp = osg::Vec3d(0, 0, 1); + + const osg::Vec3d CameraController::LocalUp = osg::Vec3d(0, 1, 0); + const osg::Vec3d CameraController::LocalLeft = osg::Vec3d(1, 0, 0); + const osg::Vec3d CameraController::LocalForward = osg::Vec3d(0, 0, 1); + + CameraController::CameraController() + : mActive(false) + , mInverted(false) + , mCameraSensitivity(1/650.f) + , mSecondaryMoveMult(50) + , mWheelMoveMult(8) + , mCamera(NULL) + { + } + + CameraController::~CameraController() + { + } + + bool CameraController::isActive() const + { + return mActive; + } + + osg::Camera* CameraController::getCamera() const + { + return mCamera; + } + + double CameraController::getCameraSensitivity() const + { + return mCameraSensitivity; + } + + bool CameraController::getInverted() const + { + return mInverted; + } + + double CameraController::getSecondaryMovementMultiplier() const + { + return mSecondaryMoveMult; + } + + double CameraController::getWheelMovementMultiplier() const + { + return mWheelMoveMult; + } + + void CameraController::setCamera(osg::Camera* camera) + { + mCamera = camera; + mActive = (mCamera != NULL); + + if (mActive) + onActivate(); + } + + void CameraController::setCameraSensitivity(double value) + { + mCameraSensitivity = value; + } + + void CameraController::setInverted(bool value) + { + mInverted = value; + } + + void CameraController::setSecondaryMovementMultiplier(double value) + { + mSecondaryMoveMult = value; + } + + void CameraController::setWheelMovementMultiplier(double value) + { + mWheelMoveMult = value; + } + + void CameraController::setup(osg::Group* root, unsigned int mask, const osg::Vec3d& up) + { + // Find World bounds + osg::ComputeBoundsVisitor boundsVisitor; + osg::BoundingBox& boundingBox = boundsVisitor.getBoundingBox(); + + boundsVisitor.setTraversalMask(mask); + root->accept(boundsVisitor); + + if (!boundingBox.valid()) + { + // Try again without any mask + boundsVisitor.reset(); + boundsVisitor.setTraversalMask(~0); + root->accept(boundsVisitor); + + // Last resort, set a default + if (!boundingBox.valid()) + { + boundingBox.set(-1, -1, -1, 1, 1, 1); + } + } + + // Calculate a good starting position + osg::Vec3d minBounds = boundingBox.corner(0) - boundingBox.center(); + osg::Vec3d maxBounds = boundingBox.corner(7) - boundingBox.center(); + + osg::Vec3d camOffset = up * maxBounds > 0 ? maxBounds : minBounds; + camOffset *= 2; + + osg::Vec3d eye = camOffset + boundingBox.center(); + osg::Vec3d center = boundingBox.center(); + + getCamera()->setViewMatrixAsLookAt(eye, center, up); + } + + /* + Free Camera Controller + */ + + FreeCameraController::FreeCameraController() + : mLockUpright(false) + , mModified(false) + , mFast(false) + , mLeft(false) + , mRight(false) + , mForward(false) + , mBackward(false) + , mRollLeft(false) + , mRollRight(false) + , mUp(LocalUp) + , mLinSpeed(1000) + , mRotSpeed(osg::PI / 2) + , mSpeedMult(8) + { + } + + double FreeCameraController::getLinearSpeed() const + { + return mLinSpeed; + } + + double FreeCameraController::getRotationalSpeed() const + { + return mRotSpeed; + } + + double FreeCameraController::getSpeedMultiplier() const + { + return mSpeedMult; + } + + void FreeCameraController::setLinearSpeed(double value) + { + mLinSpeed = value; + } + + void FreeCameraController::setRotationalSpeed(double value) + { + mRotSpeed = value; + } + + void FreeCameraController::setSpeedMultiplier(double value) + { + mSpeedMult = value; + } + + void FreeCameraController::fixUpAxis(const osg::Vec3d& up) + { + mLockUpright = true; + mUp = up; + mModified = true; + } + + void FreeCameraController::unfixUpAxis() + { + mLockUpright = false; + } + + bool FreeCameraController::handleKeyEvent(QKeyEvent* event, bool pressed) + { + if (!isActive()) + return false; + + if (event->key() == Qt::Key_Q) + { + mRollLeft = pressed; + } + else if (event->key() == Qt::Key_E) + { + mRollRight = pressed; + } + else if (event->key() == Qt::Key_A) + { + mLeft = pressed; + } + else if (event->key() == Qt::Key_D) + { + mRight = pressed; + } + else if (event->key() == Qt::Key_W) + { + mForward = pressed; + } + else if (event->key() == Qt::Key_S) + { + mBackward = pressed; + } + else if (event->key() == Qt::Key_Shift) + { + mFast = pressed; + } + else + { + return false; + } + + return true; + } + + bool FreeCameraController::handleMouseMoveEvent(std::string mode, int x, int y) + { + if (!isActive()) + return false; + + if (mode == "p-navi") + { + double scalar = getCameraSensitivity() * (getInverted() ? -1.0 : 1.0); + yaw(x * scalar); + pitch(y * scalar); + } + else if (mode == "s-navi") + { + osg::Vec3d movement; + movement += LocalLeft * -x * getSecondaryMovementMultiplier(); + movement += LocalUp * y * getSecondaryMovementMultiplier(); + + translate(movement); + } + else if (mode == "t-navi") + { + translate(LocalForward * x * (mFast ? getWheelMovementMultiplier() : 1)); + } + else + { + return false; + } + + return true; + } + + void FreeCameraController::update(double dt) + { + if (!isActive()) + return; + + double linDist = mLinSpeed * dt; + double rotDist = mRotSpeed * dt; + + if (mFast) + linDist *= mSpeedMult; + + if (mLeft) + translate(LocalLeft * linDist); + if (mRight) + translate(LocalLeft * -linDist); + if (mForward) + translate(LocalForward * linDist); + if (mBackward) + translate(LocalForward * -linDist); + + if (!mLockUpright) + { + if (mRollLeft) + roll(-rotDist); + if (mRollRight) + roll(rotDist); + } + else if(mModified) + { + stabilize(); + mModified = false; + } + + // Normalize the matrix to counter drift + getCamera()->getViewMatrix().orthoNormal(getCamera()->getViewMatrix()); + } + + void FreeCameraController::resetInput() + { + mFast = false; + mLeft = false; + mRight = false; + mForward = false; + mBackward = false; + mRollLeft = false; + mRollRight = false; + } + + void FreeCameraController::yaw(double value) + { + getCamera()->getViewMatrix() *= osg::Matrixd::rotate(value, LocalUp); + mModified = true; + } + + void FreeCameraController::pitch(double value) + { + const double Constraint = osg::PI / 2 - 0.1; + + if (mLockUpright) + { + osg::Vec3d eye, center, up; + getCamera()->getViewMatrixAsLookAt(eye, center, up); + + osg::Vec3d forward = center - eye; + osg::Vec3d left = up ^ forward; + + double pitchAngle = std::acos(up * mUp); + if ((mUp ^ up) * left < 0) + pitchAngle *= -1; + + if (std::abs(pitchAngle + value) > Constraint) + value = (pitchAngle > 0 ? 1 : -1) * Constraint - pitchAngle; + } + + getCamera()->getViewMatrix() *= osg::Matrixd::rotate(value, LocalLeft); + mModified = true; + } + + void FreeCameraController::roll(double value) + { + getCamera()->getViewMatrix() *= osg::Matrixd::rotate(value, LocalForward); + mModified = true; + } + + void FreeCameraController::translate(const osg::Vec3d& offset) + { + getCamera()->getViewMatrix() *= osg::Matrixd::translate(offset); + mModified = true; + } + + void FreeCameraController::stabilize() + { + osg::Vec3d eye, center, up; + getCamera()->getViewMatrixAsLookAt(eye, center, up); + getCamera()->setViewMatrixAsLookAt(eye, center, mUp); + } + + /* + Orbit Camera Controller + */ + + OrbitCameraController::OrbitCameraController() + : mInitialized(false) + , mFast(false) + , mLeft(false) + , mRight(false) + , mUp(false) + , mDown(false) + , mRollLeft(false) + , mRollRight(false) + , mPickingMask(~0) + , mCenter(0,0,0) + , mDistance(0) + , mOrbitSpeed(osg::PI / 4) + , mOrbitSpeedMult(4) + { + } + + osg::Vec3d OrbitCameraController::getCenter() const + { + return mCenter; + } + + double OrbitCameraController::getOrbitSpeed() const + { + return mOrbitSpeed; + } + + double OrbitCameraController::getOrbitSpeedMultiplier() const + { + return mOrbitSpeedMult; + } + + unsigned int OrbitCameraController::getPickingMask() const + { + return mPickingMask; + } + + void OrbitCameraController::setCenter(const osg::Vec3d& value) + { + osg::Vec3d eye, center, up; + getCamera()->getViewMatrixAsLookAt(eye, center, up); + + mCenter = value; + mDistance = (eye - mCenter).length(); + + getCamera()->setViewMatrixAsLookAt(eye, mCenter, up); + + mInitialized = true; + } + + void OrbitCameraController::setOrbitSpeed(double value) + { + mOrbitSpeed = value; + } + + void OrbitCameraController::setOrbitSpeedMultiplier(double value) + { + mOrbitSpeedMult = value; + } + + void OrbitCameraController::setPickingMask(unsigned int value) + { + mPickingMask = value; + } + + bool OrbitCameraController::handleKeyEvent(QKeyEvent* event, bool pressed) + { + if (!isActive()) + return false; + + if (!mInitialized) + initialize(); + + if (event->key() == Qt::Key_Q) + { + mRollLeft = pressed; + } + else if (event->key() == Qt::Key_E) + { + mRollRight = pressed; + } + else if (event->key() == Qt::Key_A) + { + mLeft = pressed; + } + else if (event->key() == Qt::Key_D) + { + mRight = pressed; + } + else if (event->key() == Qt::Key_W) + { + mUp = pressed; + } + else if (event->key() == Qt::Key_S) + { + mDown = pressed; + } + else if (event->key() == Qt::Key_Shift) + { + mFast = pressed; + } + else + { + return false; + } + + return true; + } + + bool OrbitCameraController::handleMouseMoveEvent(std::string mode, int x, int y) + { + if (!isActive()) + return false; + + if (!mInitialized) + initialize(); + + if (mode == "p-navi") + { + double scalar = getCameraSensitivity() * (getInverted() ? -1.0 : 1.0); + rotateHorizontal(x * scalar); + rotateVertical(-y * scalar); + } + else if (mode == "s-navi") + { + osg::Vec3d movement; + movement += LocalLeft * x * getSecondaryMovementMultiplier(); + movement += LocalUp * -y * getSecondaryMovementMultiplier(); + + translate(movement); + } + else if (mode == "t-navi") + { + zoom(-x * (mFast ? getWheelMovementMultiplier() : 1)); + } + else + { + return false; + } + + return true; + } + + void OrbitCameraController::update(double dt) + { + if (!isActive()) + return; + + if (!mInitialized) + initialize(); + + double rotDist = mOrbitSpeed * dt; + + if (mFast) + rotDist *= mOrbitSpeedMult; + + if (mLeft) + rotateHorizontal(-rotDist); + if (mRight) + rotateHorizontal(rotDist); + if (mUp) + rotateVertical(rotDist); + if (mDown) + rotateVertical(-rotDist); + + if (mRollLeft) + roll(-rotDist); + if (mRollRight) + roll(rotDist); + + // Normalize the matrix to counter drift + getCamera()->getViewMatrix().orthoNormal(getCamera()->getViewMatrix()); + } + + void OrbitCameraController::resetInput() + { + mFast = false; + mLeft = false; + mRight =false; + mUp = false; + mDown = false; + mRollLeft = false; + mRollRight = false; + } + + void OrbitCameraController::onActivate() + { + mInitialized = false; + } + + void OrbitCameraController::initialize() + { + static const int DefaultStartDistance = 10000.f; + + // Try to intelligently pick focus object + osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector( + osgUtil::Intersector::PROJECTION, osg::Vec3d(0, 0, 0), LocalForward)); + + intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::LIMIT_NEAREST); + osgUtil::IntersectionVisitor visitor(intersector); + + visitor.setTraversalMask(mPickingMask); + + getCamera()->accept(visitor); + + osg::Vec3d eye, center, up; + getCamera()->getViewMatrixAsLookAt(eye, center, up, DefaultStartDistance); + + if (intersector->getIntersections().begin() != intersector->getIntersections().end()) + { + mCenter = intersector->getIntersections().begin()->getWorldIntersectPoint(); + mDistance = (eye - mCenter).length(); + } + else + { + mCenter = center; + mDistance = DefaultStartDistance; + } + + mInitialized = true; + } + + void OrbitCameraController::rotateHorizontal(double value) + { + osg::Vec3d eye, center, up; + getCamera()->getViewMatrixAsLookAt(eye, center, up); + + osg::Quat rotation = osg::Quat(value, up); + osg::Vec3d oldOffset = eye - mCenter; + osg::Vec3d newOffset = rotation * oldOffset; + + getCamera()->setViewMatrixAsLookAt(mCenter + newOffset, mCenter, up); + } + + void OrbitCameraController::rotateVertical(double value) + { + osg::Vec3d eye, center, up; + getCamera()->getViewMatrixAsLookAt(eye, center, up); + + osg::Vec3d forward = center - eye; + osg::Quat rotation = osg::Quat(value, up ^ forward); + osg::Vec3d oldOffset = eye - mCenter; + osg::Vec3d newOffset = rotation * oldOffset; + + getCamera()->setViewMatrixAsLookAt(mCenter + newOffset, mCenter, up); + } + + void OrbitCameraController::roll(double value) + { + getCamera()->getViewMatrix() *= osg::Matrixd::rotate(value, LocalForward); + } + + void OrbitCameraController::translate(const osg::Vec3d& offset) + { + osg::Vec3d eye, center, up; + getCamera()->getViewMatrixAsLookAt(eye, center, up); + + osg::Vec3d newOffset = getCamera()->getViewMatrix().getRotate().inverse() * offset; + mCenter += newOffset; + eye += newOffset; + + getCamera()->setViewMatrixAsLookAt(eye, mCenter, up); + } + + void OrbitCameraController::zoom(double value) + { + mDistance = std::max(10., mDistance + value); + + osg::Vec3d eye, center, up; + getCamera()->getViewMatrixAsLookAt(eye, center, up, 1.f); + + osg::Vec3d offset = (eye - center) * mDistance; + + getCamera()->setViewMatrixAsLookAt(mCenter + offset, mCenter, up); + } +} diff --git a/apps/opencs/view/render/cameracontroller.hpp b/apps/opencs/view/render/cameracontroller.hpp new file mode 100644 index 0000000000..f9021f04bf --- /dev/null +++ b/apps/opencs/view/render/cameracontroller.hpp @@ -0,0 +1,158 @@ +#ifndef OPENCS_VIEW_CAMERACONTROLLER_H +#define OPENCS_VIEW_CAMERACONTROLLER_H + +#include + +#include +#include + +class QKeyEvent; + +namespace osg +{ + class Camera; + class Group; +} + +namespace CSVRender +{ + class CameraController + { + public: + + static const osg::Vec3d WorldUp; + + static const osg::Vec3d LocalUp; + static const osg::Vec3d LocalLeft; + static const osg::Vec3d LocalForward; + + CameraController(); + virtual ~CameraController(); + + bool isActive() const; + + osg::Camera* getCamera() const; + double getCameraSensitivity() const; + bool getInverted() const; + double getSecondaryMovementMultiplier() const; + double getWheelMovementMultiplier() const; + + void setCamera(osg::Camera*); + void setCameraSensitivity(double value); + void setInverted(bool value); + void setSecondaryMovementMultiplier(double value); + void setWheelMovementMultiplier(double value); + + // moves the camera to an intelligent position + void setup(osg::Group* root, unsigned int mask, const osg::Vec3d& up); + + virtual bool handleKeyEvent(QKeyEvent* event, bool pressed) = 0; + virtual bool handleMouseMoveEvent(std::string mode, int x, int y) = 0; + + virtual void update(double dt) = 0; + + virtual void resetInput() = 0; + + protected: + + virtual void onActivate(){} + + private: + + bool mActive, mInverted; + double mCameraSensitivity; + double mSecondaryMoveMult; + double mWheelMoveMult; + + osg::Camera* mCamera; + }; + + class FreeCameraController : public CameraController + { + public: + + FreeCameraController(); + + double getLinearSpeed() const; + double getRotationalSpeed() const; + double getSpeedMultiplier() const; + + void setLinearSpeed(double value); + void setRotationalSpeed(double value); + void setSpeedMultiplier(double value); + + void fixUpAxis(const osg::Vec3d& up); + void unfixUpAxis(); + + bool handleKeyEvent(QKeyEvent* event, bool pressed); + bool handleMouseMoveEvent(std::string mode, int x, int y); + + void update(double dt); + + void resetInput(); + + private: + + void yaw(double value); + void pitch(double value); + void roll(double value); + void translate(const osg::Vec3d& offset); + + void stabilize(); + + bool mLockUpright, mModified; + bool mFast, mLeft, mRight, mForward, mBackward, mRollLeft, mRollRight; + osg::Vec3d mUp; + + double mLinSpeed; + double mRotSpeed; + double mSpeedMult; + }; + + class OrbitCameraController : public CameraController + { + public: + + OrbitCameraController(); + + osg::Vec3d getCenter() const; + double getOrbitSpeed() const; + double getOrbitSpeedMultiplier() const; + unsigned int getPickingMask() const; + + void setCenter(const osg::Vec3d& center); + void setOrbitSpeed(double value); + void setOrbitSpeedMultiplier(double value); + void setPickingMask(unsigned int value); + + bool handleKeyEvent(QKeyEvent* event, bool pressed); + bool handleMouseMoveEvent(std::string mode, int x, int y); + + void update(double dt); + + void resetInput(); + + private: + + void onActivate(); + + void initialize(); + + void rotateHorizontal(double value); + void rotateVertical(double value); + void roll(double value); + void translate(const osg::Vec3d& offset); + void zoom(double value); + + bool mInitialized; + bool mFast, mLeft, mRight, mUp, mDown, mRollLeft, mRollRight; + unsigned int mPickingMask; + osg::Vec3d mCenter; + double mDistance; + + double mOrbitSpeed; + double mOrbitSpeedMult; + }; +} + +#endif diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index da40f7e7cb..dc22fd5110 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -1,9 +1,14 @@ #include "cell.hpp" +#include +#include +#include #include #include +#include #include +#include #include "../../model/world/idtable.hpp" #include "../../model/world/columns.hpp" @@ -11,7 +16,8 @@ #include "../../model/world/refcollection.hpp" #include "../../model/world/cellcoordinates.hpp" -#include "elements.hpp" +#include "mask.hpp" +#include "pathgrid.hpp" #include "terrainstorage.hpp" bool CSVRender::Cell::removeObject (const std::string& id) @@ -22,11 +28,18 @@ bool CSVRender::Cell::removeObject (const std::string& id) if (iter==mObjects.end()) return false; - delete iter->second; - mObjects.erase (iter); + removeObject (iter); return true; } +std::map::iterator CSVRender::Cell::removeObject ( + std::map::iterator iter) +{ + delete iter->second; + mObjects.erase (iter++); + return iter; +} + bool CSVRender::Cell::addObjects (int start, int end) { bool modified = false; @@ -43,7 +56,12 @@ bool CSVRender::Cell::addObjects (int start, int end) { std::string id = Misc::StringUtils::lowerCase (collection.getRecord (i).get().mId); - mObjects.insert (std::make_pair (id, new Object (mData, mCellNode, id, false))); + std::auto_ptr object (new Object (mData, mCellNode, id, false)); + + if (mSubModeElementMask & Mask_Reference) + object->setSubMode (mSubMode); + + mObjects.insert (std::make_pair (id, object.release())); modified = true; } } @@ -53,7 +71,8 @@ bool CSVRender::Cell::addObjects (int start, int end) CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::string& id, bool deleted) -: mData (data), mId (Misc::StringUtils::lowerCase (id)), mDeleted (deleted) +: mData (data), mId (Misc::StringUtils::lowerCase (id)), mDeleted (deleted), mSubMode (0), + mSubModeElementMask (0) { std::pair result = CSMWorld::CellCoordinates::fromId (id); @@ -63,6 +82,8 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::st mCellNode = new osg::Group; rootNode->addChild(mCellNode); + setCellMarker(); + if (!mDeleted) { CSMWorld::IdTable& references = dynamic_cast ( @@ -80,11 +101,16 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::st if (esmLand.getLandData (ESM::Land::DATA_VHGT)) { - mTerrain.reset(new Terrain::TerrainGrid(mCellNode, data.getResourceSystem().get(), NULL, new TerrainStorage(mData), Element_Terrain<<1)); + mTerrain.reset(new Terrain::TerrainGrid(mCellNode, data.getResourceSystem().get(), NULL, new TerrainStorage(mData), Mask_Terrain)); mTerrain->loadCell(esmLand.mX, esmLand.mY); + + mCellBorder.reset(new CellBorder(mCellNode, mCoordinates)); + mCellBorder->buildShape(esmLand); } } + + mPathgrid.reset(new Pathgrid(mData, mCellNode, mId, mCoordinates)); } } @@ -97,6 +123,11 @@ CSVRender::Cell::~Cell() mCellNode->getParent(0)->removeChild(mCellNode); } +CSVRender::Pathgrid* CSVRender::Cell::getPathgrid() const +{ + return mPathgrid.get(); +} + bool CSVRender::Cell::referenceableDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { @@ -161,8 +192,8 @@ bool CSVRender::Cell::referenceDataChanged (const QModelIndex& topLeft, // perform update and remove where needed bool modified = false; - for (std::map::iterator iter (mObjects.begin()); - iter!=mObjects.end(); ++iter) + std::map::iterator iter = mObjects.begin(); + while (iter!=mObjects.end()) { if (iter->second->referenceDataChanged (topLeft, bottomRight)) modified = true; @@ -171,23 +202,30 @@ bool CSVRender::Cell::referenceDataChanged (const QModelIndex& topLeft, if (iter2!=ids.end()) { - if (iter2->second) - { - removeObject (iter->first); - modified = true; - } - + bool deleted = iter2->second; ids.erase (iter2); + + if (deleted) + { + iter = removeObject (iter); + modified = true; + continue; + } } + + ++iter; } // add new objects for (std::map::iterator iter (ids.begin()); iter!=ids.end(); ++iter) { - mObjects.insert (std::make_pair ( - iter->first, new Object (mData, mCellNode, iter->first, false))); + if (!iter->second) + { + mObjects.insert (std::make_pair ( + iter->first, new Object (mData, mCellNode, iter->first, false))); - modified = true; + modified = true; + } } return modified; @@ -228,9 +266,19 @@ bool CSVRender::Cell::referenceAdded (const QModelIndex& parent, int start, int return addObjects (start, end); } +void CSVRender::Cell::pathgridModified() +{ + mPathgrid->recreateGeometry(); +} + +void CSVRender::Cell::pathgridRemoved() +{ + mPathgrid->removeGeometry(); +} + void CSVRender::Cell::setSelection (int elementMask, Selection mode) { - if (elementMask & Element_Reference) + if (elementMask & Mask_Reference) { for (std::map::const_iterator iter (mObjects.begin()); iter!=mObjects.end(); ++iter) @@ -247,6 +295,49 @@ void CSVRender::Cell::setSelection (int elementMask, Selection mode) iter->second->setSelected (selected); } } + if (elementMask & Mask_Pathgrid) + { + // Only one pathgrid may be selected, so some operations will only have an effect + // if the pathgrid is already focused + switch (mode) + { + case Selection_Clear: + mPathgrid->clearSelected(); + break; + + case Selection_All: + if (mPathgrid->isSelected()) + mPathgrid->selectAll(); + break; + + case Selection_Invert: + if (mPathgrid->isSelected()) + mPathgrid->invertSelected(); + break; + } + } +} + +void CSVRender::Cell::selectAllWithSameParentId (int elementMask) +{ + std::set ids; + + for (std::map::const_iterator iter (mObjects.begin()); + iter!=mObjects.end(); ++iter) + { + if (iter->second->getSelected()) + ids.insert (iter->second->getReferenceableId()); + } + + for (std::map::const_iterator iter (mObjects.begin()); + iter!=mObjects.end(); ++iter) + { + if (!iter->second->getSelected() && + ids.find (iter->second->getReferenceableId())!=ids.end()) + { + iter->second->setSelected (true); + } + } } void CSVRender::Cell::setCellArrows (int mask) @@ -267,6 +358,24 @@ void CSVRender::Cell::setCellArrows (int mask) } } +void CSVRender::Cell::setCellMarker() +{ + bool cellExists = false; + bool isInteriorCell = false; + + int cellIndex = mData.getCells().searchId(mId); + if (cellIndex > -1) + { + const CSMWorld::Record& cellRecord = mData.getCells().getRecord(cellIndex); + cellExists = !cellRecord.isDeleted(); + isInteriorCell = cellRecord.get().mData.mFlags & ESM::Cell::Interior; + } + + if (!isInteriorCell) { + mCellMarker.reset(new CellMarker(mCellNode, mCoordinates, cellExists)); + } +} + CSMWorld::CellCoordinates CSVRender::Cell::getCoordinates() const { return mCoordinates; @@ -276,3 +385,53 @@ bool CSVRender::Cell::isDeleted() const { return mDeleted; } + +std::vector > CSVRender::Cell::getSelection (unsigned int elementMask) const +{ + std::vector > result; + + if (elementMask & Mask_Reference) + for (std::map::const_iterator iter (mObjects.begin()); + iter!=mObjects.end(); ++iter) + if (iter->second->getSelected()) + result.push_back (iter->second->getTag()); + if (elementMask & Mask_Pathgrid) + if (mPathgrid->isSelected()) + result.push_back(mPathgrid->getTag()); + + return result; +} + +std::vector > CSVRender::Cell::getEdited (unsigned int elementMask) const +{ + std::vector > result; + + if (elementMask & Mask_Reference) + for (std::map::const_iterator iter (mObjects.begin()); + iter!=mObjects.end(); ++iter) + if (iter->second->isEdited()) + result.push_back (iter->second->getTag()); + + return result; +} + +void CSVRender::Cell::setSubMode (int subMode, unsigned int elementMask) +{ + mSubMode = subMode; + mSubModeElementMask = elementMask; + + if (elementMask & Mask_Reference) + for (std::map::const_iterator iter (mObjects.begin()); + iter!=mObjects.end(); ++iter) + iter->second->setSubMode (subMode); +} + +void CSVRender::Cell::reset (unsigned int elementMask) +{ + if (elementMask & Mask_Reference) + for (std::map::const_iterator iter (mObjects.begin()); + iter!=mObjects.end(); ++iter) + iter->second->reset(); + if (elementMask & Mask_Pathgrid) + mPathgrid->resetIndicators(); +} diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index 59f4cafee4..a5b581d24a 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -15,12 +15,16 @@ #include "object.hpp" #include "cellarrow.hpp" +#include "cellmarker.hpp" +#include "cellborder.hpp" class QModelIndex; namespace osg { class Group; + class Geometry; + class Geode; } namespace CSMWorld @@ -31,6 +35,9 @@ namespace CSMWorld namespace CSVRender { + class Pathgrid; + class TagBase; + class Cell { CSMWorld::Data& mData; @@ -40,13 +47,22 @@ namespace CSVRender std::auto_ptr mTerrain; CSMWorld::CellCoordinates mCoordinates; std::auto_ptr mCellArrows[4]; + std::auto_ptr mCellMarker; + std::auto_ptr mCellBorder; + std::auto_ptr mPathgrid; bool mDeleted; + int mSubMode; + unsigned int mSubModeElementMask; /// Ignored if cell does not have an object with the given ID. /// /// \return Was the object deleted? bool removeObject (const std::string& id); + // Remove object and return iterator to next object. + std::map::iterator removeObject ( + std::map::iterator iter); + /// Add objects from reference table that are within this cell. /// /// \return Have any objects been added? @@ -70,6 +86,9 @@ namespace CSVRender ~Cell(); + /// \note Returns the pathgrid representation which will exist as long as the cell exists + Pathgrid* getPathgrid() const; + /// \return Did this call result in a modification of the visual representation of /// this cell? bool referenceableDataChanged (const QModelIndex& topLeft, @@ -91,14 +110,35 @@ namespace CSVRender /// this cell? bool referenceAdded (const QModelIndex& parent, int start, int end); + void pathgridModified(); + + void pathgridRemoved(); + void setSelection (int elementMask, Selection mode); + // Select everything that references the same ID as at least one of the elements + // already selected + void selectAllWithSameParentId (int elementMask); + void setCellArrows (int mask); + /// \brief Set marker for this cell. + void setCellMarker(); + /// Returns 0, 0 in case of an unpaged cell. CSMWorld::CellCoordinates getCoordinates() const; bool isDeleted() const; + + std::vector > getSelection (unsigned int elementMask) const; + + std::vector > getEdited (unsigned int elementMask) const; + + void setSubMode (int subMode, unsigned int elementMask); + + /// Erase all overrides and restore the visual representation of the cell to its + /// true state. + void reset (unsigned int elementMask); }; } diff --git a/apps/opencs/view/render/cellarrow.cpp b/apps/opencs/view/render/cellarrow.cpp index 1356aa0fbd..6d8fa1c6c1 100644 --- a/apps/opencs/view/render/cellarrow.cpp +++ b/apps/opencs/view/render/cellarrow.cpp @@ -7,10 +7,10 @@ #include #include -#include "elements.hpp" +#include "mask.hpp" CSVRender::CellArrowTag::CellArrowTag (CellArrow *arrow) -: TagBase (Element_CellArrow), mArrow (arrow) +: TagBase (Mask_CellArrow), mArrow (arrow) {} CSVRender::CellArrow *CSVRender::CellArrowTag::getCellArrow() const @@ -165,8 +165,7 @@ CSVRender::CellArrow::CellArrow (osg::Group *cellNode, Direction direction, mParentNode->addChild (mBaseNode); - // 0x1 reserved for separating cull and update visitors - mBaseNode->setNodeMask (Element_CellArrow<<1); + mBaseNode->setNodeMask (Mask_CellArrow); adjustTransform(); buildShape(); diff --git a/apps/opencs/view/render/cellborder.cpp b/apps/opencs/view/render/cellborder.cpp new file mode 100644 index 0000000000..6073807ce0 --- /dev/null +++ b/apps/opencs/view/render/cellborder.cpp @@ -0,0 +1,96 @@ +#include "cellborder.hpp" + +#include +#include +#include +#include +#include + +#include + +#include "mask.hpp" + +#include "../../model/world/cellcoordinates.hpp" + +const int CSVRender::CellBorder::CellSize = ESM::Land::REAL_SIZE; +const int CSVRender::CellBorder::VertexCount = (ESM::Land::LAND_SIZE * 4) - 3; + + +CSVRender::CellBorder::CellBorder(osg::Group* cellNode, const CSMWorld::CellCoordinates& coords) + : mParentNode(cellNode) +{ + mBaseNode = new osg::PositionAttitudeTransform(); + mBaseNode->setNodeMask(Mask_CellBorder); + mBaseNode->setPosition(osg::Vec3f(coords.getX() * CellSize, coords.getY() * CellSize, 10)); + + mParentNode->addChild(mBaseNode); +} + +CSVRender::CellBorder::~CellBorder() +{ + mParentNode->removeChild(mBaseNode); +} + +void CSVRender::CellBorder::buildShape(const ESM::Land& esmLand) +{ + const ESM::Land::LandData* landData = esmLand.getLandData(ESM::Land::DATA_VHGT); + + if (!landData) + return; + + osg::ref_ptr geometry = new osg::Geometry(); + + // Vertices + osg::ref_ptr vertices = new osg::Vec3Array(); + + int x = 0, y = 0; + for (; x < ESM::Land::LAND_SIZE; ++x) + vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), landData->mHeights[landIndex(x, y)])); + + x = ESM::Land::LAND_SIZE - 1; + for (; y < ESM::Land::LAND_SIZE; ++y) + vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), landData->mHeights[landIndex(x, y)])); + + y = ESM::Land::LAND_SIZE - 1; + for (; x >= 0; --x) + vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), landData->mHeights[landIndex(x, y)])); + + x = 0; + for (; y >= 0; --y) + vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), landData->mHeights[landIndex(x, y)])); + + geometry->setVertexArray(vertices); + + // Color + osg::ref_ptr colors = new osg::Vec4Array(); + colors->push_back(osg::Vec4f(0.f, 0.5f, 0.f, 1.f)); + + geometry->setColorArray(colors, osg::Array::BIND_PER_PRIMITIVE_SET); + + // Primitive + osg::ref_ptr primitives = + new osg::DrawElementsUShort(osg::PrimitiveSet::LINE_STRIP, VertexCount+1); + + for (size_t i = 0; i < VertexCount; ++i) + primitives->setElement(i, i); + + primitives->setElement(VertexCount, 0); + + geometry->addPrimitiveSet(primitives); + geometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + + + osg::ref_ptr geode = new osg::Geode(); + geode->addDrawable(geometry); + mBaseNode->addChild(geode); +} + +size_t CSVRender::CellBorder::landIndex(int x, int y) +{ + return y * ESM::Land::LAND_SIZE + x; +} + +float CSVRender::CellBorder::scaleToWorld(int value) +{ + return (CellSize + 128) * (float)value / ESM::Land::LAND_SIZE; +} diff --git a/apps/opencs/view/render/cellborder.hpp b/apps/opencs/view/render/cellborder.hpp new file mode 100644 index 0000000000..c91aa46c69 --- /dev/null +++ b/apps/opencs/view/render/cellborder.hpp @@ -0,0 +1,54 @@ +#ifndef OPENCS_VIEW_CELLBORDER_H +#define OPENCS_VIEW_CELLBORDER_H + +#include + +#include + +namespace osg +{ + class Group; + class PositionAttitudeTransform; +} + +namespace ESM +{ + struct Land; +} + +namespace CSMWorld +{ + class CellCoordinates; +} + +namespace CSVRender +{ + + class CellBorder + { + public: + + CellBorder(osg::Group* cellNode, const CSMWorld::CellCoordinates& coords); + ~CellBorder(); + + void buildShape(const ESM::Land& esmLand); + + private: + + static const int CellSize; + static const int VertexCount; + + size_t landIndex(int x, int y); + float scaleToWorld(int val); + + // unimplemented + CellBorder(const CellBorder&); + CellBorder& operator=(const CellBorder&); + + osg::Group* mParentNode; + osg::ref_ptr mBaseNode; + + }; +} + +#endif diff --git a/apps/opencs/view/render/cellmarker.cpp b/apps/opencs/view/render/cellmarker.cpp new file mode 100644 index 0000000000..e0d270f856 --- /dev/null +++ b/apps/opencs/view/render/cellmarker.cpp @@ -0,0 +1,91 @@ +#include "cellmarker.hpp" + +#include + +#include +#include +#include +#include + +#include "mask.hpp" + +CSVRender::CellMarkerTag::CellMarkerTag(CellMarker *marker) +: TagBase(Mask_CellMarker), mMarker(marker) +{} + +CSVRender::CellMarker *CSVRender::CellMarkerTag::getCellMarker() const +{ + return mMarker; +} + +void CSVRender::CellMarker::buildMarker() +{ + const int characterSize = 20; + + // Set up attributes of marker text. + osg::ref_ptr markerText (new osgText::Text); + markerText->setLayout(osgText::Text::LEFT_TO_RIGHT); + markerText->setCharacterSize(characterSize); + markerText->setAlignment(osgText::Text::CENTER_CENTER); + markerText->setDrawMode(osgText::Text::TEXT | osgText::Text::FILLEDBOUNDINGBOX); + + // If cell exists then show black bounding box otherwise show red. + if (mExists) + { + markerText->setBoundingBoxColor(osg::Vec4f(0.0f, 0.0f, 0.0f, 1.0f)); + } + else + { + markerText->setBoundingBoxColor(osg::Vec4f(1.0f, 0.0f, 0.0f, 1.0f)); + } + + // Add text containing cell's coordinates. + std::string coordinatesText = + boost::lexical_cast(mCoordinates.getX()) + "," + + boost::lexical_cast(mCoordinates.getY()); + markerText->setText(coordinatesText); + + // Add text to marker node. + osg::ref_ptr geode (new osg::Geode); + geode->addDrawable(markerText); + mMarkerNode->addChild(geode); +} + +void CSVRender::CellMarker::positionMarker() +{ + const int cellSize = 8192; + const int markerHeight = 0; + + // Move marker to center of cell. + int x = (mCoordinates.getX() * cellSize) + (cellSize / 2); + int y = (mCoordinates.getY() * cellSize) + (cellSize / 2); + mMarkerNode->setPosition(osg::Vec3f(x, y, markerHeight)); +} + +CSVRender::CellMarker::CellMarker( + osg::Group *cellNode, + const CSMWorld::CellCoordinates& coordinates, + const bool cellExists +) : mCellNode(cellNode), + mCoordinates(coordinates), + mExists(cellExists) +{ + // Set up node for cell marker. + mMarkerNode = new osg::AutoTransform(); + mMarkerNode->setAutoRotateMode(osg::AutoTransform::ROTATE_TO_SCREEN); + mMarkerNode->setAutoScaleToScreen(true); + mMarkerNode->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + + mMarkerNode->setUserData(new CellMarkerTag(this)); + mMarkerNode->setNodeMask(Mask_CellMarker); + + mCellNode->addChild(mMarkerNode); + + buildMarker(); + positionMarker(); +} + +CSVRender::CellMarker::~CellMarker() +{ + mCellNode->removeChild(mMarkerNode); +} diff --git a/apps/opencs/view/render/cellmarker.hpp b/apps/opencs/view/render/cellmarker.hpp new file mode 100644 index 0000000000..4246b20b8d --- /dev/null +++ b/apps/opencs/view/render/cellmarker.hpp @@ -0,0 +1,68 @@ +#ifndef OPENCS_VIEW_CELLMARKER_H +#define OPENCS_VIEW_CELLMARKER_H + +#include "tagbase.hpp" + +#include + +#include "../../model/world/cellcoordinates.hpp" + +namespace osg +{ + class AutoTransform; + class Group; +} + +namespace CSVRender +{ + class CellMarker; + + class CellMarkerTag : public TagBase + { + private: + + CellMarker *mMarker; + + public: + + CellMarkerTag(CellMarker *marker); + + CellMarker *getCellMarker() const; + }; + + /// \brief Marker to display cell coordinates. + class CellMarker + { + private: + + osg::Group* mCellNode; + osg::ref_ptr mMarkerNode; + CSMWorld::CellCoordinates mCoordinates; + bool mExists; + + // Not implemented. + CellMarker(const CellMarker&); + CellMarker& operator=(const CellMarker&); + + /// \brief Build marker containing cell's coordinates. + void buildMarker(); + + /// \brief Position marker at center of cell. + void positionMarker(); + + public: + + /// \brief Constructor. + /// \param cellNode Cell to create marker for. + /// \param coordinates Coordinates of cell. + /// \param cellExists Whether or not cell exists. + CellMarker( + osg::Group *cellNode, + const CSMWorld::CellCoordinates& coordinates, + const bool cellExists); + + ~CellMarker(); + }; +} + +#endif diff --git a/apps/opencs/view/render/editmode.cpp b/apps/opencs/view/render/editmode.cpp index b325e31fb0..5f0852c909 100644 --- a/apps/opencs/view/render/editmode.cpp +++ b/apps/opencs/view/render/editmode.cpp @@ -29,37 +29,37 @@ void CSVRender::EditMode::setEditLock (bool locked) } -void CSVRender::EditMode::primaryEditPressed (osg::ref_ptr tag) {} +void CSVRender::EditMode::primaryEditPressed (const WorldspaceHitResult& hit) {} -void CSVRender::EditMode::secondaryEditPressed (osg::ref_ptr tag) {} +void CSVRender::EditMode::secondaryEditPressed (const WorldspaceHitResult& hit) {} -void CSVRender::EditMode::primarySelectPressed (osg::ref_ptr tag) {} +void CSVRender::EditMode::primarySelectPressed (const WorldspaceHitResult& hit) {} -void CSVRender::EditMode::secondarySelectPressed (osg::ref_ptr tag) {} +void CSVRender::EditMode::secondarySelectPressed (const WorldspaceHitResult& hit) {} -bool CSVRender::EditMode::primaryEditStartDrag (osg::ref_ptr tag) +bool CSVRender::EditMode::primaryEditStartDrag (const QPoint& pos) { return false; } -bool CSVRender::EditMode::secondaryEditStartDrag (osg::ref_ptr tag) +bool CSVRender::EditMode::secondaryEditStartDrag (const QPoint& pos) { return false; } -bool CSVRender::EditMode::primarySelectStartDrag (osg::ref_ptr tag) +bool CSVRender::EditMode::primarySelectStartDrag (const QPoint& pos) { return false; } -bool CSVRender::EditMode::secondarySelectStartDrag (osg::ref_ptr tag) +bool CSVRender::EditMode::secondarySelectStartDrag (const QPoint& pos) { return false; } -void CSVRender::EditMode::drag (int diffX, int diffY, double speedFactor) {} +void CSVRender::EditMode::drag (const QPoint& pos, int diffX, int diffY, double speedFactor) {} -void CSVRender::EditMode::dragCompleted() {} +void CSVRender::EditMode::dragCompleted(const QPoint& pos) {} void CSVRender::EditMode::dragAborted() {} @@ -67,6 +67,11 @@ void CSVRender::EditMode::dragWheel (int diff, double speedFactor) {} void CSVRender::EditMode::dragEnterEvent (QDragEnterEvent *event) {} -void CSVRender::EditMode::dropEvent (QDropEvent* event) {} +void CSVRender::EditMode::dropEvent (QDropEvent *event) {} void CSVRender::EditMode::dragMoveEvent (QDragMoveEvent *event) {} + +int CSVRender::EditMode::getSubMode() const +{ + return -1; +} diff --git a/apps/opencs/view/render/editmode.hpp b/apps/opencs/view/render/editmode.hpp index 3ba97cf007..f9b7027f98 100644 --- a/apps/opencs/view/render/editmode.hpp +++ b/apps/opencs/view/render/editmode.hpp @@ -8,10 +8,12 @@ class QDragEnterEvent; class QDropEvent; class QDragMoveEvent; +class QPoint; namespace CSVRender { class WorldspaceWidget; + struct WorldspaceHitResult; class TagBase; class EditMode : public CSVWidget::ModeButton @@ -38,42 +40,42 @@ namespace CSVRender virtual void setEditLock (bool locked); /// Default-implementation: Ignored. - virtual void primaryEditPressed (osg::ref_ptr tag); + virtual void primaryEditPressed (const WorldspaceHitResult& hit); /// Default-implementation: Ignored. - virtual void secondaryEditPressed (osg::ref_ptr tag); + virtual void secondaryEditPressed (const WorldspaceHitResult& hit); /// Default-implementation: Ignored. - virtual void primarySelectPressed (osg::ref_ptr tag); + virtual void primarySelectPressed (const WorldspaceHitResult& hit); /// Default-implementation: Ignored. - virtual void secondarySelectPressed (osg::ref_ptr tag); + virtual void secondarySelectPressed (const WorldspaceHitResult& hit); /// Default-implementation: ignore and return false /// /// \return Drag accepted? - virtual bool primaryEditStartDrag (osg::ref_ptr tag); + virtual bool primaryEditStartDrag (const QPoint& pos); /// Default-implementation: ignore and return false /// /// \return Drag accepted? - virtual bool secondaryEditStartDrag (osg::ref_ptr tag); + virtual bool secondaryEditStartDrag (const QPoint& pos); /// Default-implementation: ignore and return false /// /// \return Drag accepted? - virtual bool primarySelectStartDrag (osg::ref_ptr tag); + virtual bool primarySelectStartDrag (const QPoint& pos); /// Default-implementation: ignore and return false /// /// \return Drag accepted? - virtual bool secondarySelectStartDrag (osg::ref_ptr tag); + virtual bool secondarySelectStartDrag (const QPoint& pos); /// Default-implementation: ignored - virtual void drag (int diffX, int diffY, double speedFactor); + virtual void drag (const QPoint& pos, int diffX, int diffY, double speedFactor); /// Default-implementation: ignored - virtual void dragCompleted(); + virtual void dragCompleted(const QPoint& pos); /// Default-implementation: ignored /// @@ -88,10 +90,13 @@ namespace CSVRender virtual void dragEnterEvent (QDragEnterEvent *event); /// Default-implementation: ignored - virtual void dropEvent (QDropEvent* event); + virtual void dropEvent (QDropEvent *event); /// Default-implementation: ignored virtual void dragMoveEvent (QDragMoveEvent *event); + + /// Default: return -1 + virtual int getSubMode() const; }; } diff --git a/apps/opencs/view/render/elements.hpp b/apps/opencs/view/render/elements.hpp deleted file mode 100644 index 5a37956e9e..0000000000 --- a/apps/opencs/view/render/elements.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef CSV_RENDER_ELEMENTS_H -#define CSV_RENDER_ELEMENTS_H - -namespace CSVRender -{ - /// Visual elements in a scene - enum Elements - { - // elements that are part of the actual scene - Element_Reference = 0x1, - Element_Pathgrid = 0x2, - Element_Water = 0x4, - Element_Fog = 0x8, - Element_Terrain = 0x10, - - // control elements - Element_CellMarker = 0x10000, - Element_CellArrow = 0x20000, - Element_CellBorder = 0x40000 - }; -} - -#endif diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index a4d147bb4d..df5ba76217 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -1,38 +1,128 @@ #include "instancemode.hpp" +#include +#include + #include "../../model/prefs/state.hpp" -#include "elements.hpp" +#include "../../model/world/idtable.hpp" +#include "../../model/world/idtree.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/commandmacro.hpp" + +#include "../widget/scenetoolbar.hpp" +#include "../widget/scenetoolmode.hpp" + +#include "mask.hpp" + #include "object.hpp" #include "worldspacewidget.hpp" +#include "pagedworldspacewidget.hpp" +#include "instanceselectionmode.hpp" +#include "instancemovemode.hpp" + +int CSVRender::InstanceMode::getSubModeFromId (const std::string& id) const +{ + return id=="move" ? 0 : (id=="rotate" ? 1 : 2); +} CSVRender::InstanceMode::InstanceMode (WorldspaceWidget *worldspaceWidget, QWidget *parent) -: EditMode (worldspaceWidget, QIcon (":placeholder"), Element_Reference, "Instance editing", - parent) +: EditMode (worldspaceWidget, QIcon (":placeholder"), Mask_Reference, "Instance editing", + parent), mSubMode (0), mSubModeId ("move"), mSelectionMode (0), mDragMode (DragMode_None), + mDragAxis (-1), mLocked (false) { - } -void CSVRender::InstanceMode::primaryEditPressed (osg::ref_ptr tag) +void CSVRender::InstanceMode::activate (CSVWidget::SceneToolbar *toolbar) { - if (CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) - primarySelectPressed (tag); -} - -void CSVRender::InstanceMode::secondaryEditPressed (osg::ref_ptr tag) -{ - if (CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) - secondarySelectPressed (tag); -} - -void CSVRender::InstanceMode::primarySelectPressed (osg::ref_ptr tag) -{ - getWorldspaceWidget().clearSelection (Element_Reference); - - if (tag) + if (!mSubMode) { - if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) + mSubMode = new CSVWidget::SceneToolMode (toolbar, "Edit Sub-Mode"); + mSubMode->addButton (new InstanceMoveMode (this), "move"); + mSubMode->addButton (":placeholder", "rotate", + "Rotate selected instances" + "
  • Use primary edit to rotate instances freely
  • " + "
  • Use secondary edit to rotate instances within the grid
  • " + "
" + "Not implemented yet"); + mSubMode->addButton (":placeholder", "scale", + "Scale selected instances" + "
  • Use primary edit to scale instances freely
  • " + "
  • Use secondary edit to scale instances along the grid
  • " + "
" + "Not implemented yet"); + + mSubMode->setButton (mSubModeId); + + connect (mSubMode, SIGNAL (modeChanged (const std::string&)), + this, SLOT (subModeChanged (const std::string&))); + } + + if (!mSelectionMode) + mSelectionMode = new InstanceSelectionMode (toolbar, getWorldspaceWidget()); + + mDragMode = DragMode_None; + + EditMode::activate (toolbar); + + toolbar->addTool (mSubMode); + toolbar->addTool (mSelectionMode); + + std::string subMode = mSubMode->getCurrentId(); + + getWorldspaceWidget().setSubMode (getSubModeFromId (subMode), Mask_Reference); +} + +void CSVRender::InstanceMode::deactivate (CSVWidget::SceneToolbar *toolbar) +{ + mDragMode = DragMode_None; + getWorldspaceWidget().reset (Mask_Reference); + + if (mSelectionMode) + { + toolbar->removeTool (mSelectionMode); + delete mSelectionMode; + mSelectionMode = 0; + } + + if (mSubMode) + { + toolbar->removeTool (mSubMode); + delete mSubMode; + mSubMode = 0; + } + + EditMode::deactivate (toolbar); +} + +void CSVRender::InstanceMode::setEditLock (bool locked) +{ + mLocked = locked; + + if (mLocked) + getWorldspaceWidget().abortDrag(); +} + +void CSVRender::InstanceMode::primaryEditPressed (const WorldspaceHitResult& hit) +{ + if (CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) + primarySelectPressed (hit); +} + +void CSVRender::InstanceMode::secondaryEditPressed (const WorldspaceHitResult& hit) +{ + if (CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) + secondarySelectPressed (hit); +} + +void CSVRender::InstanceMode::primarySelectPressed (const WorldspaceHitResult& hit) +{ + getWorldspaceWidget().clearSelection (Mask_Reference); + + if (hit.tag) + { + if (CSVRender::ObjectTag *objectTag = dynamic_cast (hit.tag.get())) { // hit an Object, select it CSVRender::Object* object = objectTag->mObject; @@ -42,11 +132,11 @@ void CSVRender::InstanceMode::primarySelectPressed (osg::ref_ptr tag) } } -void CSVRender::InstanceMode::secondarySelectPressed (osg::ref_ptr tag) +void CSVRender::InstanceMode::secondarySelectPressed (const WorldspaceHitResult& hit) { - if (tag) + if (hit.tag) { - if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) + if (CSVRender::ObjectTag *objectTag = dynamic_cast (hit.tag.get())) { // hit an Object, toggle its selection state CSVRender::Object* object = objectTag->mObject; @@ -55,3 +145,302 @@ void CSVRender::InstanceMode::secondarySelectPressed (osg::ref_ptr tag) } } } + +bool CSVRender::InstanceMode::primaryEditStartDrag (const QPoint& pos) +{ + if (mDragMode!=DragMode_None || mLocked) + return false; + + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + if (hit.tag && CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) + { + getWorldspaceWidget().clearSelection (Mask_Reference); + if (CSVRender::ObjectTag *objectTag = dynamic_cast (hit.tag.get())) + { + CSVRender::Object* object = objectTag->mObject; + object->setSelected (true); + } + } + + std::vector > selection = + getWorldspaceWidget().getSelection (Mask_Reference); + + if (selection.empty()) + return false; + + for (std::vector >::iterator iter (selection.begin()); + iter!=selection.end(); ++iter) + { + if (CSVRender::ObjectTag *objectTag = dynamic_cast (iter->get())) + { + objectTag->mObject->setEdited (Object::Override_Position); + } + } + + // \todo check for sub-mode + + if (CSVRender::ObjectMarkerTag *objectTag = dynamic_cast (hit.tag.get())) + { + mDragAxis = objectTag->mAxis; + } + else + mDragAxis = -1; + + mDragMode = DragMode_Move; + + return true; +} + +bool CSVRender::InstanceMode::secondaryEditStartDrag (const QPoint& pos) +{ + if (mLocked) + return false; + + return false; +} + +void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, double speedFactor) +{ + osg::Vec3f eye; + osg::Vec3f centre; + osg::Vec3f up; + + getWorldspaceWidget().getCamera()->getViewMatrix().getLookAt (eye, centre, up); + + osg::Vec3f offset; + + if (diffY) + offset += up * diffY * speedFactor; + + if (diffX) + offset += ((centre-eye) ^ up) * diffX * speedFactor; + + switch (mDragMode) + { + case DragMode_Move: + { + if (mDragAxis!=-1) + for (int i=0; i<3; ++i) + if (i!=mDragAxis) + offset[i] = 0; + + std::vector > selection = + getWorldspaceWidget().getEdited (Mask_Reference); + + for (std::vector >::iterator iter (selection.begin()); + iter!=selection.end(); ++iter) + { + if (CSVRender::ObjectTag *objectTag = dynamic_cast (iter->get())) + { + ESM::Position position = objectTag->mObject->getPosition(); + for (int i=0; i<3; ++i) + position.pos[i] += offset[i]; + objectTag->mObject->setPosition (position.pos); + } + } + + break; + } + + case DragMode_None: break; + } +} + +void CSVRender::InstanceMode::dragCompleted(const QPoint& pos) +{ + std::vector > selection = + getWorldspaceWidget().getEdited (Mask_Reference); + + QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); + + QString description; + + switch (mDragMode) + { + case DragMode_Move: description = "Move Instances"; break; + + case DragMode_None: break; + } + + + CSMWorld::CommandMacro macro (undoStack, description); + + for (std::vector >::iterator iter (selection.begin()); + iter!=selection.end(); ++iter) + { + if (CSVRender::ObjectTag *objectTag = dynamic_cast (iter->get())) + { + objectTag->mObject->apply (macro); + } + } + + mDragMode = DragMode_None; +} + +void CSVRender::InstanceMode::dragAborted() +{ + getWorldspaceWidget().reset (Mask_Reference); + mDragMode = DragMode_None; +} + +void CSVRender::InstanceMode::dragWheel (int diff, double speedFactor) +{ + if (mDragMode==DragMode_Move) + { + osg::Vec3f eye; + osg::Vec3f centre; + osg::Vec3f up; + + getWorldspaceWidget().getCamera()->getViewMatrix().getLookAt (eye, centre, up); + + osg::Vec3f offset = centre - eye; + offset.normalize(); + offset *= diff * speedFactor; + + std::vector > selection = + getWorldspaceWidget().getEdited (Mask_Reference); + + for (std::vector >::iterator iter (selection.begin()); + iter!=selection.end(); ++iter) + { + if (CSVRender::ObjectTag *objectTag = dynamic_cast (iter->get())) + { + ESM::Position position = objectTag->mObject->getPosition(); + for (int i=0; i<3; ++i) + position.pos[i] += offset[i]; + objectTag->mObject->setPosition (position.pos); + } + } + } +} + +void CSVRender::InstanceMode::dragEnterEvent (QDragEnterEvent *event) +{ + if (const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData())) + { + if (!mime->fromDocument (getWorldspaceWidget().getDocument())) + return; + + if (mime->holdsType (CSMWorld::UniversalId::Type_Referenceable)) + event->accept(); + } +} + +void CSVRender::InstanceMode::dropEvent (QDropEvent* event) +{ + if (const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData())) + { + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + + if (!mime->fromDocument (document)) + return; + + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (event->pos(), getWorldspaceWidget().getInteractionMask()); + + std::string cellId = getWorldspaceWidget().getCellId (hit.worldPos); + + CSMWorld::IdTree& cellTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); + + bool noCell = document.getData().getCells().searchId (cellId)==-1; + + if (noCell) + { + std::string mode = CSMPrefs::get()["Scene Drops"]["outside-drop"].toString(); + + // target cell does not exist + if (mode=="Discard") + return; + + if (mode=="Create cell and insert") + { + std::auto_ptr createCommand ( + new CSMWorld::CreateCommand (cellTable, cellId)); + + int parentIndex = cellTable.findColumnIndex (CSMWorld::Columns::ColumnId_Cell); + int index = cellTable.findNestedColumnIndex (parentIndex, CSMWorld::Columns::ColumnId_Interior); + createCommand->addNestedValue (parentIndex, index, false); + + document.getUndoStack().push (createCommand.release()); + + if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + CSMWorld::CellSelection selection = paged->getCellSelection(); + selection.add (CSMWorld::CellCoordinates::fromId (cellId).first); + paged->setCellSelection (selection); + } + + noCell = false; + } + } + else if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + CSMWorld::CellSelection selection = paged->getCellSelection(); + if (!selection.has (CSMWorld::CellCoordinates::fromId (cellId).first)) + { + // target cell exists, but is not shown + std::string mode = + CSMPrefs::get()["Scene Drops"]["outside-visible-drop"].toString(); + + if (mode=="Discard") + return; + + if (mode=="Show cell and insert") + { + selection.add (CSMWorld::CellCoordinates::fromId (cellId).first); + paged->setCellSelection (selection); + } + } + } + + CSMWorld::IdTable& referencesTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_References)); + + bool dropped = false; + + std::vector ids = mime->getData(); + + for (std::vector::const_iterator iter (ids.begin()); + iter!=ids.end(); ++iter) + if (mime->isReferencable (iter->getType())) + { + // create reference + std::auto_ptr createCommand ( + new CSMWorld::CreateCommand ( + referencesTable, document.getData().getReferences().getNewId())); + + createCommand->addValue (referencesTable.findColumnIndex ( + CSMWorld::Columns::ColumnId_Cell), QString::fromUtf8 (cellId.c_str())); + createCommand->addValue (referencesTable.findColumnIndex ( + CSMWorld::Columns::ColumnId_PositionXPos), hit.worldPos.x()); + createCommand->addValue (referencesTable.findColumnIndex ( + CSMWorld::Columns::ColumnId_PositionYPos), hit.worldPos.y()); + createCommand->addValue (referencesTable.findColumnIndex ( + CSMWorld::Columns::ColumnId_PositionZPos), hit.worldPos.z()); + createCommand->addValue (referencesTable.findColumnIndex ( + CSMWorld::Columns::ColumnId_ReferenceableId), + QString::fromUtf8 (iter->getId().c_str())); + + document.getUndoStack().push (createCommand.release()); + + dropped = true; + } + + if (dropped) + event->accept(); + } +} + +int CSVRender::InstanceMode::getSubMode() const +{ + return mSubMode ? getSubModeFromId (mSubMode->getCurrentId()) : 0; +} + +void CSVRender::InstanceMode::subModeChanged (const std::string& id) +{ + mSubModeId = id; + getWorldspaceWidget().abortDrag(); + getWorldspaceWidget().setSubMode (getSubModeFromId (id), Mask_Reference); +} diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index 66451bd996..4f7937b510 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -3,23 +3,75 @@ #include "editmode.hpp" +namespace CSVWidget +{ + class SceneToolMode; +} + namespace CSVRender { + class InstanceSelectionMode; + class InstanceMode : public EditMode { Q_OBJECT + enum DragMode + { + DragMode_None, + DragMode_Move + }; + + CSVWidget::SceneToolMode *mSubMode; + std::string mSubModeId; + InstanceSelectionMode *mSelectionMode; + DragMode mDragMode; + int mDragAxis; + bool mLocked; + + int getSubModeFromId (const std::string& id) const; + public: InstanceMode (WorldspaceWidget *worldspaceWidget, QWidget *parent = 0); - virtual void primaryEditPressed (osg::ref_ptr tag); + virtual void activate (CSVWidget::SceneToolbar *toolbar); - virtual void secondaryEditPressed (osg::ref_ptr tag); + virtual void deactivate (CSVWidget::SceneToolbar *toolbar); - virtual void primarySelectPressed (osg::ref_ptr tag); + virtual void setEditLock (bool locked); - virtual void secondarySelectPressed (osg::ref_ptr tag); + virtual void primaryEditPressed (const WorldspaceHitResult& hit); + + virtual void secondaryEditPressed (const WorldspaceHitResult& hit); + + virtual void primarySelectPressed (const WorldspaceHitResult& hit); + + virtual void secondarySelectPressed (const WorldspaceHitResult& hit); + + virtual bool primaryEditStartDrag (const QPoint& pos); + + virtual bool secondaryEditStartDrag (const QPoint& pos); + + virtual void drag (const QPoint& pos, int diffX, int diffY, double speedFactor); + + virtual void dragCompleted(const QPoint& pos); + + /// \note dragAborted will not be called, if the drag is aborted via changing + /// editing mode + virtual void dragAborted(); + + virtual void dragWheel (int diff, double speedFactor); + + virtual void dragEnterEvent (QDragEnterEvent *event); + + virtual void dropEvent (QDropEvent *event); + + virtual int getSubMode() const; + + private slots: + + void subModeChanged (const std::string& id); }; } diff --git a/apps/opencs/view/render/instancemovemode.cpp b/apps/opencs/view/render/instancemovemode.cpp new file mode 100644 index 0000000000..9929f5fcb4 --- /dev/null +++ b/apps/opencs/view/render/instancemovemode.cpp @@ -0,0 +1,12 @@ + +#include "instancemovemode.hpp" + +CSVRender::InstanceMoveMode::InstanceMoveMode (QWidget *parent) +: ModeButton (QIcon (QPixmap (":placeholder")), + "Move selected instances" + "
  • Use primary edit to move instances around freely
  • " + "
  • Use secondary edit to move instances around within the grid
  • " + "
" + "Grid move not implemented yet", + parent) +{} diff --git a/apps/opencs/view/render/instancemovemode.hpp b/apps/opencs/view/render/instancemovemode.hpp new file mode 100644 index 0000000000..bd0e28dac8 --- /dev/null +++ b/apps/opencs/view/render/instancemovemode.hpp @@ -0,0 +1,18 @@ +#ifndef CSV_RENDER_INSTANCEMOVEMODE_H +#define CSV_RENDER_INSTANCEMOVEMODE_H + +#include "../widget/modebutton.hpp" + +namespace CSVRender +{ + class InstanceMoveMode : public CSVWidget::ModeButton + { + Q_OBJECT + + public: + + InstanceMoveMode (QWidget *parent = 0); + }; +} + +#endif diff --git a/apps/opencs/view/render/instanceselectionmode.cpp b/apps/opencs/view/render/instanceselectionmode.cpp new file mode 100644 index 0000000000..bf8ede0eb4 --- /dev/null +++ b/apps/opencs/view/render/instanceselectionmode.cpp @@ -0,0 +1,57 @@ +#include "instanceselectionmode.hpp" + +#include +#include + +#include "../../model/world/idtable.hpp" +#include "../../model/world/commands.hpp" + +#include "worldspacewidget.hpp" +#include "object.hpp" + +namespace CSVRender +{ + InstanceSelectionMode::InstanceSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget) + : SelectionMode(parent, worldspaceWidget, Mask_Reference) + { + mSelectSame = new QAction("Extend selection to instances with same object ID", this); + mDeleteSelection = new QAction("Delete selected instances", this); + + connect(mSelectSame, SIGNAL(triggered()), this, SLOT(selectSame())); + connect(mDeleteSelection, SIGNAL(triggered()), this, SLOT(deleteSelection())); + } + + bool InstanceSelectionMode::createContextMenu(QMenu* menu) + { + if (menu) + { + SelectionMode::createContextMenu(menu); + + menu->addAction(mSelectSame); + menu->addAction(mDeleteSelection); + } + + return true; + } + + void InstanceSelectionMode::selectSame() + { + getWorldspaceWidget().selectAllWithSameParentId(Mask_Reference); + } + + void InstanceSelectionMode::deleteSelection() + { + std::vector > selection = getWorldspaceWidget().getSelection(Mask_Reference); + + CSMWorld::IdTable& referencesTable = dynamic_cast( + *getWorldspaceWidget().getDocument().getData().getTableModel(CSMWorld::UniversalId::Type_References)); + + for (std::vector >::iterator iter = selection.begin(); iter != selection.end(); ++iter) + { + CSMWorld::DeleteCommand* command = new CSMWorld::DeleteCommand(referencesTable, + static_cast(iter->get())->mObject->getReferenceId()); + + getWorldspaceWidget().getDocument().getUndoStack().push(command); + } + } +} diff --git a/apps/opencs/view/render/instanceselectionmode.hpp b/apps/opencs/view/render/instanceselectionmode.hpp new file mode 100644 index 0000000000..a590240d97 --- /dev/null +++ b/apps/opencs/view/render/instanceselectionmode.hpp @@ -0,0 +1,38 @@ +#ifndef CSV_RENDER_INSTANCE_SELECTION_MODE_H +#define CSV_RENDER_INSTANCE_SELECTION_MODE_H + +#include "selectionmode.hpp" + +namespace CSVRender +{ + class InstanceSelectionMode : public SelectionMode + { + Q_OBJECT + + public: + + InstanceSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget); + + protected: + + /// Add context menu items to \a menu. + /// + /// \attention menu can be a 0-pointer + /// + /// \return Have there been any menu items to be added (if menu is 0 and there + /// items to be added, the function must return true anyway. + bool createContextMenu(QMenu* menu); + + private: + + QAction* mDeleteSelection; + QAction* mSelectSame; + + private slots: + + void deleteSelection(); + void selectSame(); + }; +} + +#endif diff --git a/apps/opencs/view/render/mask.hpp b/apps/opencs/view/render/mask.hpp new file mode 100644 index 0000000000..55b7c823f3 --- /dev/null +++ b/apps/opencs/view/render/mask.hpp @@ -0,0 +1,35 @@ +#ifndef CSV_RENDER_ELEMENTS_H +#define CSV_RENDER_ELEMENTS_H + +namespace CSVRender +{ + + /// Node masks used on the OSG scene graph in OpenMW-CS. + /// @note See the respective file in OpenMW (apps/openmw/mwrender/vismask.hpp) + /// for general usage hints about node masks. + /// @copydoc MWRender::VisMask + enum Mask + { + // internal use within NifLoader, do not change + Mask_UpdateVisitor = 0x1, + + // elements that are part of the actual scene + Mask_Reference = 0x2, + Mask_Pathgrid = 0x4, + Mask_Water = 0x8, + Mask_Fog = 0x10, + Mask_Terrain = 0x20, + + // used within models + Mask_ParticleSystem = 0x100, + + Mask_Lighting = 0x200, + + // control elements + Mask_CellMarker = 0x10000, + Mask_CellArrow = 0x20000, + Mask_CellBorder = 0x40000 + }; +} + +#endif diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index c295a023a8..cc151ead90 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -9,17 +9,25 @@ #include #include #include +#include +#include #include #include "../../model/world/data.hpp" #include "../../model/world/ref.hpp" #include "../../model/world/refidcollection.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/universalid.hpp" +#include "../../model/world/commandmacro.hpp" +#include "../../model/world/cellcoordinates.hpp" #include -#include +#include +#include +#include -#include "elements.hpp" +#include "mask.hpp" namespace { @@ -39,7 +47,7 @@ namespace CSVRender::ObjectTag::ObjectTag (Object* object) -: TagBase (Element_Reference), mObject (object) +: TagBase (Mask_Reference), mObject (object) {} QString CSVRender::ObjectTag::getToolTip (bool hideBasics) const @@ -48,6 +56,11 @@ QString CSVRender::ObjectTag::getToolTip (bool hideBasics) const } +CSVRender::ObjectMarkerTag::ObjectMarkerTag (Object* object, int axis) +: ObjectTag (object), mAxis (axis) +{} + + void CSVRender::Object::clear() { } @@ -62,6 +75,7 @@ void CSVRender::Object::update() const CSMWorld::RefIdCollection& referenceables = mData.getReferenceables(); int index = referenceables.searchId (mReferenceableId); + const ESM::Light* light = NULL; if (index==-1) error = 1; @@ -73,6 +87,14 @@ void CSVRender::Object::update() referenceables.findColumnIndex (CSMWorld::Columns::ColumnId_Model)). toString().toUtf8().constData(); + int recordType = + referenceables.getData (index, + referenceables.findColumnIndex(CSMWorld::Columns::ColumnId_RecordType)).toInt(); + if (recordType == CSMWorld::UniversalId::Type_Light) + { + light = &dynamic_cast& >(referenceables.getRecord(index)).get(); + } + if (model.empty()) error = 2; } @@ -89,7 +111,7 @@ void CSVRender::Object::update() { std::string path = "meshes\\" + model; - mResourceSystem->getSceneManager()->createInstance(path, mBaseNode); + mResourceSystem->getSceneManager()->getInstance(path, mBaseNode); } catch (std::exception& e) { @@ -97,6 +119,21 @@ void CSVRender::Object::update() std::cerr << e.what() << std::endl; } } + + if (light) + { + const Fallback::Map* fallback = mData.getFallbackMap(); + static bool outQuadInLin = fallback->getFallbackBool("LightAttenuation_OutQuadInLin"); + static bool useQuadratic = fallback->getFallbackBool("LightAttenuation_UseQuadratic"); + static float quadraticValue = fallback->getFallbackFloat("LightAttenuation_QuadraticValue"); + static float quadraticRadiusMult = fallback->getFallbackFloat("LightAttenuation_QuadraticRadiusMult"); + static bool useLinear = fallback->getFallbackBool("LightAttenuation_UseLinear"); + static float linearRadiusMult = fallback->getFallbackFloat("LightAttenuation_LinearRadiusMult"); + static float linearValue = fallback->getFallbackFloat("LightAttenuation_LinearValue"); + bool isExterior = false; // FIXME + SceneUtil::addLight(mBaseNode, light, Mask_ParticleSystem, Mask_Lighting, isExterior, outQuadInLin, useQuadratic, + quadraticValue, quadraticRadiusMult, useLinear, linearRadiusMult, linearValue); + } } void CSVRender::Object::adjustTransform() @@ -104,18 +141,20 @@ void CSVRender::Object::adjustTransform() if (mReferenceId.empty()) return; - const CSMWorld::CellRef& reference = getReference(); + ESM::Position position = getPosition(); // position - mBaseNode->setPosition(mForceBaseToZero ? osg::Vec3() : osg::Vec3f(reference.mPos.pos[0], reference.mPos.pos[1], reference.mPos.pos[2])); + mRootNode->setPosition(mForceBaseToZero ? osg::Vec3() : osg::Vec3f(position.pos[0], position.pos[1], position.pos[2])); // orientation - osg::Quat xr (-reference.mPos.rot[0], osg::Vec3f(1,0,0)); - osg::Quat yr (-reference.mPos.rot[1], osg::Vec3f(0,1,0)); - osg::Quat zr (-reference.mPos.rot[2], osg::Vec3f(0,0,1)); + osg::Quat xr (-position.rot[0], osg::Vec3f(1,0,0)); + osg::Quat yr (-position.rot[1], osg::Vec3f(0,1,0)); + osg::Quat zr (-position.rot[2], osg::Vec3f(0,0,1)); mBaseNode->setAttitude(zr*yr*xr); - mBaseNode->setScale(osg::Vec3(reference.mScale, reference.mScale, reference.mScale)); + float scale = getScale(); + + mBaseNode->setScale(osg::Vec3(scale, scale, scale)); } const CSMWorld::CellRef& CSVRender::Object::getReference() const @@ -126,20 +165,159 @@ const CSMWorld::CellRef& CSVRender::Object::getReference() const return mData.getReferences().getRecord (mReferenceId).get(); } +void CSVRender::Object::updateMarker() +{ + for (int i=0; i<3; ++i) + { + if (mMarker[i]) + { + mRootNode->removeChild (mMarker[i]); + mMarker[i] = osg::ref_ptr(); + } + + if (mSelected) + { + if (mSubMode==0) + { + mMarker[i] = makeMarker (i); + mMarker[i]->setUserData(new ObjectMarkerTag (this, i)); + + mRootNode->addChild (mMarker[i]); + } + } + } +} + +osg::ref_ptr CSVRender::Object::makeMarker (int axis) +{ + osg::ref_ptr geometry (new osg::Geometry); + + const float shaftWidth = 10; + const float shaftBaseLength = 50; + const float headWidth = 30; + const float headLength = 30; + + float shaftLength = shaftBaseLength + mBaseNode->getBound().radius(); + + // shaft + osg::Vec3Array *vertices = new osg::Vec3Array; + + for (int i=0; i<2; ++i) + { + float length = i ? shaftLength : 0; + + vertices->push_back (getMarkerPosition (-shaftWidth/2, -shaftWidth/2, length, axis)); + vertices->push_back (getMarkerPosition (-shaftWidth/2, shaftWidth/2, length, axis)); + vertices->push_back (getMarkerPosition (shaftWidth/2, shaftWidth/2, length, axis)); + vertices->push_back (getMarkerPosition (shaftWidth/2, -shaftWidth/2, length, axis)); + } + + // head backside + vertices->push_back (getMarkerPosition (-headWidth/2, -headWidth/2, shaftLength, axis)); + vertices->push_back (getMarkerPosition (-headWidth/2, headWidth/2, shaftLength, axis)); + vertices->push_back (getMarkerPosition (headWidth/2, headWidth/2, shaftLength, axis)); + vertices->push_back (getMarkerPosition (headWidth/2, -headWidth/2, shaftLength, axis)); + + // head + vertices->push_back (getMarkerPosition (0, 0, shaftLength+headLength, axis)); + + geometry->setVertexArray (vertices); + + osg::DrawElementsUShort *primitives = new osg::DrawElementsUShort (osg::PrimitiveSet::TRIANGLES, 0); + + // shaft + for (int i=0; i<4; ++i) + { + int i2 = i==3 ? 0 : i+1; + primitives->push_back (i); + primitives->push_back (4+i); + primitives->push_back (i2); + + primitives->push_back (4+i); + primitives->push_back (4+i2); + primitives->push_back (i2); + } + + // cap + primitives->push_back (0); + primitives->push_back (1); + primitives->push_back (2); + + primitives->push_back (2); + primitives->push_back (3); + primitives->push_back (0); + + // head, backside + primitives->push_back (0+8); + primitives->push_back (1+8); + primitives->push_back (2+8); + + primitives->push_back (2+8); + primitives->push_back (3+8); + primitives->push_back (0+8); + + for (int i=0; i<4; ++i) + { + primitives->push_back (12); + primitives->push_back (8+(i==3 ? 0 : i+1)); + primitives->push_back (8+i); + } + + geometry->addPrimitiveSet (primitives); + + osg::Vec4Array *colours = new osg::Vec4Array; + + for (int i=0; i<8; ++i) + colours->push_back (osg::Vec4f (axis==0 ? 1.0f : 0.2f, axis==1 ? 1.0f : 0.2f, + axis==2 ? 1.0f : 0.2f, 1.0f)); + + for (int i=8; i<8+4+1; ++i) + colours->push_back (osg::Vec4f (axis==0 ? 1.0f : 0.0f, axis==1 ? 1.0f : 0.0f, + axis==2 ? 1.0f : 0.0f, 1.0f)); + + geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX); + + geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF); + + osg::ref_ptr geode (new osg::Geode); + geode->addDrawable (geometry); + + return geode; +} + +osg::Vec3f CSVRender::Object::getMarkerPosition (float x, float y, float z, int axis) +{ + switch (axis) + { + case 2: return osg::Vec3f (x, y, z); + case 0: return osg::Vec3f (z, x, y); + case 1: return osg::Vec3f (y, z, x); + + default: + + throw std::logic_error ("invalid axis for marker geometry"); + } +} + CSVRender::Object::Object (CSMWorld::Data& data, osg::Group* parentNode, const std::string& id, bool referenceable, bool forceBaseToZero) -: mData (data), mBaseNode(0), mSelected(false), mParentNode(parentNode), mResourceSystem(data.getResourceSystem().get()), mForceBaseToZero (forceBaseToZero) +: mData (data), mBaseNode(0), mSelected(false), mParentNode(parentNode), mResourceSystem(data.getResourceSystem().get()), mForceBaseToZero (forceBaseToZero), + mScaleOverride (1), mOverrideFlags (0), mSubMode (-1) { + mRootNode = new osg::PositionAttitudeTransform; + mBaseNode = new osg::PositionAttitudeTransform; + mBaseNode->addCullCallback(new SceneUtil::LightListCallback); + mOutline = new osgFX::Scribe; - mOutline->addChild(mBaseNode); mBaseNode->setUserData(new ObjectTag(this)); - parentNode->addChild(mBaseNode); + mRootNode->addChild (mBaseNode); - // 0x1 reserved for separating cull and update visitors - mBaseNode->setNodeMask(Element_Reference<<1); + parentNode->addChild (mRootNode); + + mRootNode->setNodeMask(Mask_Reference); if (referenceable) { @@ -153,25 +331,32 @@ CSVRender::Object::Object (CSMWorld::Data& data, osg::Group* parentNode, adjustTransform(); update(); + updateMarker(); } CSVRender::Object::~Object() { clear(); - mParentNode->removeChild(mBaseNode); + mParentNode->removeChild (mRootNode); } void CSVRender::Object::setSelected(bool selected) { mSelected = selected; - mParentNode->removeChild(mOutline); - mParentNode->removeChild(mBaseNode); + mOutline->removeChild(mBaseNode); + mRootNode->removeChild(mOutline); + mRootNode->removeChild(mBaseNode); if (selected) - mParentNode->addChild(mOutline); + { + mOutline->addChild(mBaseNode); + mRootNode->addChild(mOutline); + } else - mParentNode->addChild(mBaseNode); + mRootNode->addChild(mBaseNode); + + updateMarker(); } bool CSVRender::Object::getSelected() const @@ -190,6 +375,7 @@ bool CSVRender::Object::referenceableDataChanged (const QModelIndex& topLeft, { adjustTransform(); update(); + updateMarker(); return true; } @@ -240,6 +426,7 @@ bool CSVRender::Object::referenceDataChanged (const QModelIndex& topLeft, references.getData (index, columnIndex).toString().toUtf8().constData(); update(); + updateMarker(); } return true; @@ -257,3 +444,153 @@ std::string CSVRender::Object::getReferenceableId() const { return mReferenceableId; } + +osg::ref_ptr CSVRender::Object::getTag() const +{ + return static_cast (mBaseNode->getUserData()); +} + +bool CSVRender::Object::isEdited() const +{ + return mOverrideFlags; +} + +void CSVRender::Object::setEdited (int flags) +{ + bool discard = mOverrideFlags & ~flags; + int added = flags & ~mOverrideFlags; + + mOverrideFlags = flags; + + if (added & Override_Position) + for (int i=0; i<3; ++i) + mPositionOverride.pos[i] = getReference().mPos.pos[i]; + + if (added & Override_Rotation) + for (int i=0; i<3; ++i) + mPositionOverride.rot[i] = getReference().mPos.rot[i]; + + if (added & Override_Scale) + mScaleOverride = getReference().mScale; + + if (discard) + adjustTransform(); +} + +ESM::Position CSVRender::Object::getPosition() const +{ + ESM::Position position = getReference().mPos; + + if (mOverrideFlags & Override_Position) + for (int i=0; i<3; ++i) + position.pos[i] = mPositionOverride.pos[i]; + + if (mOverrideFlags & Override_Rotation) + for (int i=0; i<3; ++i) + position.rot[i] = mPositionOverride.rot[i]; + + return position; +} + +float CSVRender::Object::getScale() const +{ + return mOverrideFlags & Override_Scale ? mScaleOverride : getReference().mScale; +} + +void CSVRender::Object::setPosition (const float position[3]) +{ + mOverrideFlags |= Override_Position; + + for (int i=0; i<3; ++i) + mPositionOverride.pos[i] = position[i]; + + adjustTransform(); +} + +void CSVRender::Object::setRotation (const float rotation[3]) +{ + mOverrideFlags |= Override_Rotation; + + for (int i=0; i<3; ++i) + mPositionOverride.rot[i] = rotation[i]; + + adjustTransform(); +} + +void CSVRender::Object::setScale (float scale) +{ + mOverrideFlags |= Override_Scale; + + mScaleOverride = scale; + + adjustTransform(); +} + +void CSVRender::Object::apply (CSMWorld::CommandMacro& commands) +{ + const CSMWorld::RefCollection& collection = mData.getReferences(); + QAbstractItemModel *model = mData.getTableModel (CSMWorld::UniversalId::Type_References); + + int recordIndex = collection.getIndex (mReferenceId); + + if (mOverrideFlags & Override_Position) + { + for (int i=0; i<3; ++i) + { + int column = collection.findColumnIndex (static_cast ( + CSMWorld::Columns::ColumnId_PositionXPos+i)); + + commands.push (new CSMWorld::ModifyCommand (*model, + model->index (recordIndex, column), mPositionOverride.pos[i])); + } + + int column = collection.findColumnIndex (static_cast ( + CSMWorld::Columns::ColumnId_Cell)); + + std::pair cellIndex = collection.getRecord (recordIndex).get().getCellIndex(); + + /// \todo figure out worldspace (not important until multiple worldspaces are supported) + std::string cellId = CSMWorld::CellCoordinates (cellIndex).getId (""); + + commands.push (new CSMWorld::ModifyCommand (*model, + model->index (recordIndex, column), QString::fromUtf8 (cellId.c_str()))); + } + + if (mOverrideFlags & Override_Rotation) + { + for (int i=0; i<3; ++i) + { + int column = collection.findColumnIndex (static_cast ( + CSMWorld::Columns::ColumnId_PositionXRot+i)); + + commands.push (new CSMWorld::ModifyCommand (*model, + model->index (recordIndex, column), mPositionOverride.rot[i])); + } + } + + if (mOverrideFlags & Override_Scale) + { + int column = collection.findColumnIndex (CSMWorld::Columns::ColumnId_Scale); + + commands.push (new CSMWorld::ModifyCommand (*model, + model->index (recordIndex, column), mScaleOverride)); + } + + mOverrideFlags = 0; +} + +void CSVRender::Object::setSubMode (int subMode) +{ + if (subMode!=mSubMode) + { + mSubMode = subMode; + updateMarker(); + } +} + +void CSVRender::Object::reset() +{ + mOverrideFlags = 0; + adjustTransform(); + updateMarker(); +} diff --git a/apps/opencs/view/render/object.hpp b/apps/opencs/view/render/object.hpp index e7638e7a99..54a284fa3b 100644 --- a/apps/opencs/view/render/object.hpp +++ b/apps/opencs/view/render/object.hpp @@ -8,14 +8,19 @@ #include #include +#include + #include "tagbase.hpp" class QModelIndex; +class QUndoStack; namespace osg { class PositionAttitudeTransform; class Group; + class Node; + class Geode; } namespace osgFX @@ -32,6 +37,7 @@ namespace CSMWorld { class Data; struct CellRef; + class CommandMacro; } namespace CSVRender @@ -50,18 +56,43 @@ namespace CSVRender virtual QString getToolTip (bool hideBasics) const; }; + class ObjectMarkerTag : public ObjectTag + { + public: + + ObjectMarkerTag (Object* object, int axis); + + int mAxis; + }; class Object { - const CSMWorld::Data& mData; + public: + + enum OverrideFlags + { + Override_Position = 1, + Override_Rotation = 2, + Override_Scale = 4 + }; + + private: + + CSMWorld::Data& mData; std::string mReferenceId; std::string mReferenceableId; + osg::ref_ptr mRootNode; osg::ref_ptr mBaseNode; osg::ref_ptr mOutline; bool mSelected; osg::Group* mParentNode; Resource::ResourceSystem* mResourceSystem; bool mForceBaseToZero; + ESM::Position mPositionOverride; + int mScaleOverride; + int mOverrideFlags; + osg::ref_ptr mMarker[3]; + int mSubMode; /// Not implemented Object (const Object&); @@ -82,6 +113,12 @@ namespace CSVRender /// Throws an exception if *this was constructed with referenceable const CSMWorld::CellRef& getReference() const; + void updateMarker(); + + osg::ref_ptr makeMarker (int axis); + + osg::Vec3f getMarkerPosition (float x, float y, float z, int axis); + public: Object (CSMWorld::Data& data, osg::Group *cellNode, @@ -114,6 +151,35 @@ namespace CSVRender std::string getReferenceId() const; std::string getReferenceableId() const; + + osg::ref_ptr getTag() const; + + /// Is there currently an editing operation running on this object? + bool isEdited() const; + + void setEdited (int flags); + + ESM::Position getPosition() const; + + float getScale() const; + + /// Set override position. + void setPosition (const float position[3]); + + /// Set override rotation + void setRotation (const float rotation[3]); + + /// Set override scale + void setScale (float scale); + + /// Apply override changes via command and end edit mode + void apply (CSMWorld::CommandMacro& commands); + + void setSubMode (int subMode); + + /// Erase all overrides and restore the visual representation of the object to its + /// true state. + void reset(); }; } diff --git a/apps/opencs/view/render/orbitcameramode.cpp b/apps/opencs/view/render/orbitcameramode.cpp new file mode 100644 index 0000000000..e6f3612c6c --- /dev/null +++ b/apps/opencs/view/render/orbitcameramode.cpp @@ -0,0 +1,37 @@ +#include "orbitcameramode.hpp" + +#include + +#include "worldspacewidget.hpp" + +namespace CSVRender +{ + OrbitCameraMode::OrbitCameraMode(WorldspaceWidget* worldspaceWidget, const QIcon& icon, const QString& tooltip, + QWidget* parent) + : ModeButton(icon, tooltip, parent) + , mWorldspaceWidget(worldspaceWidget) + , mCenterOnSelection(0) + { + } + + void OrbitCameraMode::activate(CSVWidget::SceneToolbar* toolbar) + { + mCenterOnSelection = new QAction("Center on selected object", this); + connect(mCenterOnSelection, SIGNAL(triggered()), this, SLOT(centerSelection())); + } + + bool OrbitCameraMode::createContextMenu(QMenu* menu) + { + if (menu) + { + menu->addAction(mCenterOnSelection); + } + + return true; + } + + void OrbitCameraMode::centerSelection() + { + mWorldspaceWidget->centerOrbitCameraOnSelection(); + } +} diff --git a/apps/opencs/view/render/orbitcameramode.hpp b/apps/opencs/view/render/orbitcameramode.hpp new file mode 100644 index 0000000000..cd8387084b --- /dev/null +++ b/apps/opencs/view/render/orbitcameramode.hpp @@ -0,0 +1,33 @@ +#ifndef CSV_RENDER_ORBITCAMERAPICKMODE_H +#define CSV_RENDER_ORBITCAMERAPICKMODE_H + +#include "../widget/modebutton.hpp" + +namespace CSVRender +{ + class WorldspaceWidget; + + class OrbitCameraMode : public CSVWidget::ModeButton + { + Q_OBJECT + + public: + + OrbitCameraMode(WorldspaceWidget* worldspaceWidget, const QIcon& icon, const QString& tooltip = "", + QWidget* parent = 0); + + virtual void activate(CSVWidget::SceneToolbar* toolbar); + virtual bool createContextMenu(QMenu* menu); + + private: + + WorldspaceWidget* mWorldspaceWidget; + QAction* mCenterOnSelection; + + private slots: + + void centerSelection(); + }; +} + +#endif diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index dae5990e6f..a01df43926 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -6,24 +6,21 @@ #include #include -#include - #include #include "../../model/world/tablemimedata.hpp" #include "../../model/world/idtable.hpp" -#include "../widget/scenetooltoggle.hpp" +#include "../widget/scenetooltoggle2.hpp" #include "../widget/scenetoolmode.hpp" #include "../widget/scenetooltoggle2.hpp" #include "editmode.hpp" -#include "elements.hpp" +#include "mask.hpp" bool CSVRender::PagedWorldspaceWidget::adjustCells() { bool modified = false; - bool wasEmpty = mCells.empty(); const CSMWorld::IdCollection& cells = mDocument.getData().getCells(); @@ -114,11 +111,6 @@ bool CSVRender::PagedWorldspaceWidget::adjustCells() } } - /// \todo do not overwrite manipulator object - /// \todo move code to useViewHint function - if (modified && wasEmpty) - mView->setCameraManipulator(new osgGA::TrackballManipulator); - return modified; } @@ -126,8 +118,8 @@ void CSVRender::PagedWorldspaceWidget::addVisibilitySelectorButtons ( CSVWidget::SceneToolToggle2 *tool) { WorldspaceWidget::addVisibilitySelectorButtons (tool); - tool->addButton (Element_Terrain, "Terrain"); - tool->addButton (Element_Fog, "Fog", "", true); + tool->addButton (Button_Terrain, Mask_Terrain, "Terrain"); + tool->addButton (Button_Fog, Mask_Fog, "Fog", "", true); } void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons ( @@ -137,27 +129,28 @@ void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons ( /// \todo replace EditMode with suitable subclasses tool->addButton ( - new EditMode (this, QIcon (":placeholder"), Element_Reference, "Terrain shape editing"), + new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain shape editing"), "terrain-shape"); tool->addButton ( - new EditMode (this, QIcon (":placeholder"), Element_Reference, "Terrain texture editing"), + new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain texture editing"), "terrain-texture"); tool->addButton ( - new EditMode (this, QIcon (":placeholder"), Element_Reference, "Terrain vertex paint editing"), + new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain vertex paint editing"), "terrain-vertex"); tool->addButton ( - new EditMode (this, QIcon (":placeholder"), Element_Reference, "Terrain movement"), + new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain movement"), "terrain-move"); } -void CSVRender::PagedWorldspaceWidget::handleMouseClick (osg::ref_ptr tag, const std::string& button, bool shift) +void CSVRender::PagedWorldspaceWidget::handleMouseClick (const WorldspaceHitResult& hit, const std::string& button, + bool shift) { - if (tag && tag->getElement()==Element_CellArrow) + if (hit.tag && hit.tag->getMask()==Mask_CellArrow) { if (button=="p-edit" || button=="s-edit") { if (CellArrowTag *cellArrowTag = - dynamic_cast (tag.get())) + dynamic_cast (hit.tag.get())) { CellArrow *arrow = cellArrowTag->getCellArrow(); @@ -217,7 +210,7 @@ void CSVRender::PagedWorldspaceWidget::handleMouseClick (osg::ref_ptr t } } - WorldspaceWidget::handleMouseClick (tag, button, shift); + WorldspaceWidget::handleMouseClick (hit, button, shift); } void CSVRender::PagedWorldspaceWidget::referenceableDataChanged (const QModelIndex& topLeft, @@ -283,6 +276,82 @@ void CSVRender::PagedWorldspaceWidget::referenceAdded (const QModelIndex& parent flagAsModified(); } +void CSVRender::PagedWorldspaceWidget::pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); + + int rowStart = -1; + int rowEnd = -1; + + if (topLeft.parent().isValid()) + { + rowStart = topLeft.parent().row(); + rowEnd = bottomRight.parent().row(); + } + else + { + rowStart = topLeft.row(); + rowEnd = bottomRight.row(); + } + + for (int row = rowStart; row <= rowEnd; ++row) + { + const CSMWorld::Pathgrid& pathgrid = pathgrids.getRecord(row).get(); + CSMWorld::CellCoordinates coords = CSMWorld::CellCoordinates(pathgrid.mData.mX, pathgrid.mData.mY); + + std::map::iterator searchResult = mCells.find(coords); + if (searchResult != mCells.end()) + { + searchResult->second->pathgridModified(); + flagAsModified(); + } + } +} + +void CSVRender::PagedWorldspaceWidget::pathgridAboutToBeRemoved (const QModelIndex& parent, int start, int end) +{ + const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); + + if (!parent.isValid()) + { + // Pathgrid going to be deleted + for (int row = start; row <= end; ++row) + { + const CSMWorld::Pathgrid& pathgrid = pathgrids.getRecord(row).get(); + CSMWorld::CellCoordinates coords = CSMWorld::CellCoordinates(pathgrid.mData.mX, pathgrid.mData.mY); + + std::map::iterator searchResult = mCells.find(coords); + if (searchResult != mCells.end()) + { + searchResult->second->pathgridRemoved(); + flagAsModified(); + } + } + } +} + +void CSVRender::PagedWorldspaceWidget::pathgridAdded(const QModelIndex& parent, int start, int end) +{ + const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); + + if (!parent.isValid()) + { + for (int row = start; row <= end; ++row) + { + const CSMWorld::Pathgrid& pathgrid = pathgrids.getRecord(row).get(); + CSMWorld::CellCoordinates coords = CSMWorld::CellCoordinates(pathgrid.mData.mX, pathgrid.mData.mY); + + std::map::iterator searchResult = mCells.find(coords); + if (searchResult != mCells.end()) + { + searchResult->second->pathgridModified(); + flagAsModified(); + } + } + } +} + + std::string CSVRender::PagedWorldspaceWidget::getStartupInstruction() { osg::Vec3d eye, center, up; @@ -309,10 +378,13 @@ void CSVRender::PagedWorldspaceWidget::addCellToScene ( bool deleted = index==-1 || cells.getRecord (index).mState==CSMWorld::RecordBase::State_Deleted; - Cell *cell = new Cell (mDocument.getData(), mRootNode, coordinates.getId (mWorldspace), - deleted); + std::auto_ptr cell ( + new Cell (mDocument.getData(), mRootNode, coordinates.getId (mWorldspace), + deleted)); + EditMode *editMode = getEditMode(); + cell->setSubMode (editMode->getSubMode(), editMode->getInteractionMask()); - mCells.insert (std::make_pair (coordinates, cell)); + mCells.insert (std::make_pair (coordinates, cell.release())); } void CSVRender::PagedWorldspaceWidget::removeCellFromScene ( @@ -432,6 +504,11 @@ void CSVRender::PagedWorldspaceWidget::setCellSelection (const CSMWorld::CellSel emit cellSelectionChanged (mSelection); } +const CSMWorld::CellSelection& CSVRender::PagedWorldspaceWidget::getCellSelection() const +{ + return mSelection; +} + std::pair< int, int > CSVRender::PagedWorldspaceWidget::getCoordinatesFromId (const std::string& record) const { std::istringstream stream (record.c_str()); @@ -492,7 +569,7 @@ CSVRender::WorldspaceWidget::dropRequirments CSVRender::PagedWorldspaceWidget::g unsigned int CSVRender::PagedWorldspaceWidget::getVisibilityMask() const { - return WorldspaceWidget::getVisibilityMask() | mControlElements->getSelection(); + return WorldspaceWidget::getVisibilityMask() | mControlElements->getSelectionMask(); } void CSVRender::PagedWorldspaceWidget::clearSelection (int elementMask) @@ -504,18 +581,118 @@ void CSVRender::PagedWorldspaceWidget::clearSelection (int elementMask) flagAsModified(); } -CSVWidget::SceneToolToggle *CSVRender::PagedWorldspaceWidget::makeControlVisibilitySelector ( +void CSVRender::PagedWorldspaceWidget::invertSelection (int elementMask) +{ + for (std::map::iterator iter = mCells.begin(); + iter!=mCells.end(); ++iter) + iter->second->setSelection (elementMask, Cell::Selection_Invert); + + flagAsModified(); +} + +void CSVRender::PagedWorldspaceWidget::selectAll (int elementMask) +{ + for (std::map::iterator iter = mCells.begin(); + iter!=mCells.end(); ++iter) + iter->second->setSelection (elementMask, Cell::Selection_All); + + flagAsModified(); +} + +void CSVRender::PagedWorldspaceWidget::selectAllWithSameParentId (int elementMask) +{ + for (std::map::iterator iter = mCells.begin(); + iter!=mCells.end(); ++iter) + iter->second->selectAllWithSameParentId (elementMask); + + flagAsModified(); +} + +std::string CSVRender::PagedWorldspaceWidget::getCellId (const osg::Vec3f& point) const +{ + const int cellSize = 8192; + + CSMWorld::CellCoordinates cellCoordinates ( + static_cast (std::floor (point.x()/cellSize)), + static_cast (std::floor (point.y()/cellSize))); + + return cellCoordinates.getId (mWorldspace); +} + +CSVRender::Cell* CSVRender::PagedWorldspaceWidget::getCell(const osg::Vec3d& point) const +{ + const int cellSize = 8192; + + CSMWorld::CellCoordinates coords( + static_cast (std::floor (point.x()/cellSize)), + static_cast (std::floor (point.y()/cellSize))); + + std::map::const_iterator searchResult = mCells.find(coords); + if (searchResult != mCells.end()) + return searchResult->second; + else + return 0; +} + +std::vector > CSVRender::PagedWorldspaceWidget::getSelection ( + unsigned int elementMask) const +{ + std::vector > result; + + for (std::map::const_iterator iter = mCells.begin(); + iter!=mCells.end(); ++iter) + { + std::vector > cellResult = + iter->second->getSelection (elementMask); + + result.insert (result.end(), cellResult.begin(), cellResult.end()); + } + + return result; +} + +std::vector > CSVRender::PagedWorldspaceWidget::getEdited ( + unsigned int elementMask) const +{ + std::vector > result; + + for (std::map::const_iterator iter = mCells.begin(); + iter!=mCells.end(); ++iter) + { + std::vector > cellResult = + iter->second->getEdited (elementMask); + + result.insert (result.end(), cellResult.begin(), cellResult.end()); + } + + return result; +} + +void CSVRender::PagedWorldspaceWidget::setSubMode (int subMode, unsigned int elementMask) +{ + for (std::map::const_iterator iter = mCells.begin(); + iter!=mCells.end(); ++iter) + iter->second->setSubMode (subMode, elementMask); +} + +void CSVRender::PagedWorldspaceWidget::reset (unsigned int elementMask) +{ + for (std::map::const_iterator iter = mCells.begin(); + iter!=mCells.end(); ++iter) + iter->second->reset (elementMask); +} + +CSVWidget::SceneToolToggle2 *CSVRender::PagedWorldspaceWidget::makeControlVisibilitySelector ( CSVWidget::SceneToolbar *parent) { - mControlElements = new CSVWidget::SceneToolToggle (parent, - "Controls & Guides Visibility", ":placeholder"); + mControlElements = new CSVWidget::SceneToolToggle2 (parent, + "Controls & Guides Visibility", ":scenetoolbar/scene-view-marker-c", ":scenetoolbar/scene-view-marker-"); - mControlElements->addButton (":placeholder", Element_CellMarker, ":placeholder", - "Cell marker"); - mControlElements->addButton (":placeholder", Element_CellArrow, ":placeholder", "Cell arrows"); - mControlElements->addButton (":placeholder", Element_CellBorder, ":placeholder", "Cell border"); + mControlElements->addButton (1, Mask_CellMarker, "Cell Marker"); + mControlElements->addButton (2, Mask_CellArrow, "Cell Arrows"); + mControlElements->addButton (4, Mask_CellBorder, "Cell Border"); - mControlElements->setSelection (0xffffffff); + mControlElements->setSelectionMask (0xffffffff); connect (mControlElements, SIGNAL (selectionChanged()), this, SLOT (elementSelectionChanged())); diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index e983fddd55..6920007087 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -11,6 +11,7 @@ namespace CSVWidget { class SceneToolToggle; + class SceneToolToggle2; } namespace CSVRender @@ -26,7 +27,7 @@ namespace CSVRender CSMWorld::CellSelection mSelection; std::map mCells; std::string mWorldspace; - CSVWidget::SceneToolToggle *mControlElements; + CSVWidget::SceneToolToggle2 *mControlElements; bool mDisplayCellCoord; private: @@ -51,6 +52,12 @@ namespace CSVRender virtual void referenceAdded (const QModelIndex& index, int start, int end); + virtual void pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + virtual void pathgridAboutToBeRemoved (const QModelIndex& parent, int start, int end); + + virtual void pathgridAdded (const QModelIndex& parent, int start, int end); + virtual std::string getStartupInstruction(); /// \note Does not update the view or any cell marker @@ -80,6 +87,8 @@ namespace CSVRender void setCellSelection(const CSMWorld::CellSelection& selection); + const CSMWorld::CellSelection& getCellSelection() const; + /// \return Drop handled? virtual bool handleDrop (const std::vector& data, DropType type); @@ -88,7 +97,7 @@ namespace CSVRender /// \attention The created tool is not added to the toolbar (via addTool). Doing /// that is the responsibility of the calling function. - virtual CSVWidget::SceneToolToggle *makeControlVisibilitySelector ( + virtual CSVWidget::SceneToolToggle2 *makeControlVisibilitySelector ( CSVWidget::SceneToolbar *parent); virtual unsigned int getVisibilityMask() const; @@ -96,13 +105,40 @@ namespace CSVRender /// \param elementMask Elements to be affected by the clear operation virtual void clearSelection (int elementMask); + /// \param elementMask Elements to be affected by the select operation + virtual void invertSelection (int elementMask); + + /// \param elementMask Elements to be affected by the select operation + virtual void selectAll (int elementMask); + + // Select everything that references the same ID as at least one of the elements + // already selected + // + /// \param elementMask Elements to be affected by the select operation + virtual void selectAllWithSameParentId (int elementMask); + + virtual std::string getCellId (const osg::Vec3f& point) const; + + virtual Cell* getCell(const osg::Vec3d& point) const; + + virtual std::vector > getSelection (unsigned int elementMask) + const; + + virtual std::vector > getEdited (unsigned int elementMask) + const; + + virtual void setSubMode (int subMode, unsigned int elementMask); + + /// Erase all overrides and restore the visual representation to its true state. + virtual void reset (unsigned int elementMask); + protected: virtual void addVisibilitySelectorButtons (CSVWidget::SceneToolToggle2 *tool); virtual void addEditModeSelectorButtons (CSVWidget::SceneToolMode *tool); - virtual void handleMouseClick (osg::ref_ptr tag, const std::string& button, bool shift); + virtual void handleMouseClick (const WorldspaceHitResult& hit, const std::string& button, bool shift); signals: diff --git a/apps/opencs/view/render/pathgrid.cpp b/apps/opencs/view/render/pathgrid.cpp new file mode 100644 index 0000000000..9eb2765d38 --- /dev/null +++ b/apps/opencs/view/render/pathgrid.cpp @@ -0,0 +1,680 @@ +#include "pathgrid.hpp" + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "../../model/world/cell.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/commandmacro.hpp" +#include "../../model/world/data.hpp" +#include "../../model/world/idtree.hpp" + +namespace CSVRender +{ + class PathgridNodeCallback : public osg::NodeCallback + { + public: + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + PathgridTag* tag = static_cast(node->getUserData()); + tag->getPathgrid()->update(); + } + }; + + PathgridTag::PathgridTag(Pathgrid* pathgrid) + : TagBase(Mask_Pathgrid), mPathgrid(pathgrid) + { + } + + Pathgrid* PathgridTag::getPathgrid() const + { + return mPathgrid; + } + + QString PathgridTag::getToolTip(bool hideBasics) const + { + QString text("Pathgrid: "); + text += mPathgrid->getId().c_str(); + + return text; + } + + Pathgrid::Pathgrid(CSMWorld::Data& data, osg::Group* parent, const std::string& pathgridId, + const CSMWorld::CellCoordinates& coordinates) + : mData(data) + , mPathgridCollection(mData.getPathgrids()) + , mId(pathgridId) + , mCoords(coordinates) + , mInterior(false) + , mDragOrigin(0) + , mChangeGeometry(true) + , mRemoveGeometry(false) + , mUseOffset(true) + , mParent(parent) + , mPathgridGeometry(0) + , mDragGeometry(0) + , mTag(new PathgridTag(this)) + { + const float CoordScalar = ESM::Land::REAL_SIZE; + + mBaseNode = new osg::PositionAttitudeTransform (); + mBaseNode->setPosition(osg::Vec3f(mCoords.getX() * CoordScalar, mCoords.getY() * CoordScalar, 0.f)); + mBaseNode->setUserData(mTag); + mBaseNode->setUpdateCallback(new PathgridNodeCallback()); + mBaseNode->setNodeMask(Mask_Pathgrid); + mParent->addChild(mBaseNode); + + mPathgridGeode = new osg::Geode(); + mBaseNode->addChild(mPathgridGeode); + + recreateGeometry(); + + int index = mData.getCells().searchId(mId); + if (index != -1) + { + const CSMWorld::Cell& cell = mData.getCells().getRecord(index).get(); + mInterior = cell.mData.mFlags & ESM::Cell::Interior; + } + } + + Pathgrid::~Pathgrid() + { + mParent->removeChild(mBaseNode); + } + + const CSMWorld::CellCoordinates& Pathgrid::getCoordinates() const + { + return mCoords; + } + + const std::string& Pathgrid::getId() const + { + return mId; + } + + bool Pathgrid::isSelected() const + { + return !mSelected.empty(); + } + + const Pathgrid::NodeList& Pathgrid::getSelected() const + { + return mSelected; + } + + void Pathgrid::selectAll() + { + mSelected.clear(); + + const CSMWorld::Pathgrid* source = getPathgridSource(); + if (source) + { + for (unsigned short i = 0; i < static_cast(source->mPoints.size()); ++i) + mSelected.push_back(i); + + createSelectedGeometry(*source); + } + else + { + removeSelectedGeometry(); + } + } + + void Pathgrid::toggleSelected(unsigned short node) + { + NodeList::iterator searchResult = std::find(mSelected.begin(), mSelected.end(), node); + if (searchResult != mSelected.end()) + { + mSelected.erase(searchResult); + } + else + { + mSelected.push_back(node); + } + + createSelectedGeometry(); + } + + void Pathgrid::invertSelected() + { + NodeList temp = NodeList(mSelected); + mSelected.clear(); + + const CSMWorld::Pathgrid* source = getPathgridSource(); + if (source) + { + for (unsigned short i = 0; i < static_cast(source->mPoints.size()); ++i) + { + if (std::find(temp.begin(), temp.end(), i) == temp.end()) + mSelected.push_back(i); + } + + createSelectedGeometry(*source); + } + else + { + removeSelectedGeometry(); + } + } + + void Pathgrid::clearSelected() + { + mSelected.clear(); + removeSelectedGeometry(); + } + + void Pathgrid::moveSelected(const osg::Vec3d& offset) + { + mUseOffset = true; + mMoveOffset += offset; + + recreateGeometry(); + } + + void Pathgrid::setDragOrigin(unsigned short node) + { + mDragOrigin = node; + } + + void Pathgrid::setDragEndpoint(unsigned short node) + { + const CSMWorld::Pathgrid* source = getPathgridSource(); + if (source) + { + const CSMWorld::Pathgrid::Point& pointA = source->mPoints[mDragOrigin]; + const CSMWorld::Pathgrid::Point& pointB = source->mPoints[node]; + + osg::Vec3f start = osg::Vec3f(pointA.mX, pointA.mY, pointA.mZ + SceneUtil::DiamondHalfHeight); + osg::Vec3f end = osg::Vec3f(pointB.mX, pointB.mY, pointB.mZ + SceneUtil::DiamondHalfHeight); + + createDragGeometry(start, end, true); + } + } + + void Pathgrid::setDragEndpoint(const osg::Vec3d& pos) + { + const CSMWorld::Pathgrid* source = getPathgridSource(); + if (source) + { + const CSMWorld::Pathgrid::Point& point = source->mPoints[mDragOrigin]; + + osg::Vec3f start = osg::Vec3f(point.mX, point.mY, point.mZ + SceneUtil::DiamondHalfHeight); + osg::Vec3f end = pos - mBaseNode->getPosition(); + createDragGeometry(start, end, false); + } + } + + void Pathgrid::resetIndicators() + { + mUseOffset = false; + mMoveOffset.set(0, 0, 0); + + mPathgridGeode->removeDrawable(mDragGeometry); + mDragGeometry = 0; + } + + void Pathgrid::applyPoint(CSMWorld::CommandMacro& commands, const osg::Vec3d& worldPos) + { + CSMWorld::IdTree* model = dynamic_cast(mData.getTableModel( + CSMWorld::UniversalId::Type_Pathgrids)); + + const CSMWorld::Pathgrid* source = getPathgridSource(); + if (source) + { + osg::Vec3d localCoords = worldPos - mBaseNode->getPosition(); + + int posX = clampToCell(static_cast(localCoords.x())); + int posY = clampToCell(static_cast(localCoords.y())); + int posZ = clampToCell(static_cast(localCoords.z())); + + int recordIndex = mPathgridCollection.getIndex (mId); + int parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridPoints); + + int posXColumn = mPathgridCollection.searchNestedColumnIndex(parentColumn, + CSMWorld::Columns::ColumnId_PathgridPosX); + + int posYColumn = mPathgridCollection.searchNestedColumnIndex(parentColumn, + CSMWorld::Columns::ColumnId_PathgridPosY); + + int posZColumn = mPathgridCollection.searchNestedColumnIndex(parentColumn, + CSMWorld::Columns::ColumnId_PathgridPosZ); + + QModelIndex parent = model->index(recordIndex, parentColumn); + int row = static_cast(source->mPoints.size()); + + // Add node to end of list + commands.push(new CSMWorld::AddNestedCommand(*model, mId, row, parentColumn)); + commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, posXColumn, parent), posX)); + commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, posYColumn, parent), posY)); + commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, posZColumn, parent), posZ)); + } + else + { + int index = mPathgridCollection.searchId(mId); + if (index == -1) + { + // Does not exist + commands.push(new CSMWorld::CreatePathgridCommand(*model, mId)); + } + else + { + source = &mPathgridCollection.getRecord(index).get(); + + // Deleted, so revert and remove all data + commands.push(new CSMWorld::RevertCommand(*model, mId)); + + int parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridPoints); + for (int row = source->mPoints.size() - 1; row >= 0; --row) + { + commands.push(new CSMWorld::DeleteNestedCommand(*model, mId, row, parentColumn)); + } + + parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridEdges); + for (int row = source->mEdges.size() - 1; row >= 0; --row) + { + commands.push(new CSMWorld::DeleteNestedCommand(*model, mId, row, parentColumn)); + } + } + } + } + + void Pathgrid::applyPosition(CSMWorld::CommandMacro& commands) + { + const CSMWorld::Pathgrid* source = getPathgridSource(); + if (source) + { + osg::Vec3d localCoords = mMoveOffset; + + int offsetX = static_cast(localCoords.x()); + int offsetY = static_cast(localCoords.y()); + int offsetZ = static_cast(localCoords.z()); + + QAbstractItemModel* model = mData.getTableModel(CSMWorld::UniversalId::Type_Pathgrids); + + int recordIndex = mPathgridCollection.getIndex(mId); + int parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridPoints); + + int posXColumn = mPathgridCollection.searchNestedColumnIndex(parentColumn, + CSMWorld::Columns::ColumnId_PathgridPosX); + + int posYColumn = mPathgridCollection.searchNestedColumnIndex(parentColumn, + CSMWorld::Columns::ColumnId_PathgridPosY); + + int posZColumn = mPathgridCollection.searchNestedColumnIndex(parentColumn, + CSMWorld::Columns::ColumnId_PathgridPosZ); + + QModelIndex parent = model->index(recordIndex, parentColumn); + + for (size_t i = 0; i < mSelected.size(); ++i) + { + const CSMWorld::Pathgrid::Point& point = source->mPoints[mSelected[i]]; + int row = static_cast(mSelected[i]); + + commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, posXColumn, parent), + clampToCell(point.mX + offsetX))); + + commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, posYColumn, parent), + clampToCell(point.mY + offsetY))); + + commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, posZColumn, parent), + clampToCell(point.mZ + offsetZ))); + } + } + } + + void Pathgrid::applyEdge(CSMWorld::CommandMacro& commands, unsigned short node1, unsigned short node2) + { + const CSMWorld::Pathgrid* source = getPathgridSource(); + if (source) + { + addEdge(commands, *source, node1, node2); + } + } + + void Pathgrid::applyEdges(CSMWorld::CommandMacro& commands, unsigned short node) + { + const CSMWorld::Pathgrid* source = getPathgridSource(); + if (source) + { + for (size_t i = 0; i < mSelected.size(); ++i) + { + addEdge(commands, *source, node, mSelected[i]); + } + } + } + + void Pathgrid::applyRemoveNodes(CSMWorld::CommandMacro& commands) + { + const CSMWorld::Pathgrid* source = getPathgridSource(); + if (source) + { + CSMWorld::IdTree* model = dynamic_cast(mData.getTableModel( + CSMWorld::UniversalId::Type_Pathgrids)); + + // Want to remove nodes from end of list first + std::sort(mSelected.begin(), mSelected.end(), std::greater()); + + int recordIndex = mPathgridCollection.getIndex(mId); + int parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridPoints); + + for (std::vector::iterator row = mSelected.begin(); row != mSelected.end(); ++row) + { + commands.push(new CSMWorld::DeleteNestedCommand(*model, mId, static_cast(*row), parentColumn)); + } + + // Fix/remove edges + std::set > edgeRowsToRemove; + + parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridEdges); + + int edge0Column = mPathgridCollection.searchNestedColumnIndex(parentColumn, + CSMWorld::Columns::ColumnId_PathgridEdge0); + + int edge1Column = mPathgridCollection.searchNestedColumnIndex(parentColumn, + CSMWorld::Columns::ColumnId_PathgridEdge1); + + QModelIndex parent = model->index(recordIndex, parentColumn); + + for (size_t edge = 0; edge < source->mEdges.size(); ++edge) + { + int adjustment0 = 0; + int adjustment1 = 0; + + // Determine necessary adjustment + for (std::vector::iterator point = mSelected.begin(); point != mSelected.end(); ++point) + { + if (source->mEdges[edge].mV0 == *point || source->mEdges[edge].mV1 == *point) + { + edgeRowsToRemove.insert(static_cast(edge)); + + adjustment0 = 0; // No need to adjust, its getting removed + adjustment1 = 0; + break; + } + + if (source->mEdges[edge].mV0 > *point) + --adjustment0; + + if (source->mEdges[edge].mV1 > *point) + --adjustment1; + } + + if (adjustment0 != 0) + { + int adjustedEdge = source->mEdges[edge].mV0 + adjustment0; + commands.push(new CSMWorld::ModifyCommand(*model, model->index(edge, edge0Column, parent), + adjustedEdge)); + } + + if (adjustment1 != 0) + { + int adjustedEdge = source->mEdges[edge].mV1 + adjustment1; + commands.push(new CSMWorld::ModifyCommand(*model, model->index(edge, edge1Column, parent), + adjustedEdge)); + } + } + + std::set >::iterator row; + for (row = edgeRowsToRemove.begin(); row != edgeRowsToRemove.end(); ++row) + { + commands.push(new CSMWorld::DeleteNestedCommand(*model, mId, *row, parentColumn)); + } + } + + clearSelected(); + } + + void Pathgrid::applyRemoveEdges(CSMWorld::CommandMacro& commands) + { + const CSMWorld::Pathgrid* source = getPathgridSource(); + if (source) + { + // Want to remove from end of row first + std::set > rowsToRemove; + for (size_t i = 0; i <= mSelected.size(); ++i) + { + for (size_t j = i + 1; j < mSelected.size(); ++j) + { + int row = edgeExists(*source, mSelected[i], mSelected[j]); + if (row != -1) + { + rowsToRemove.insert(row); + } + + row = edgeExists(*source, mSelected[j], mSelected[i]); + if (row != -1) + { + rowsToRemove.insert(row); + } + } + } + + CSMWorld::IdTree* model = dynamic_cast(mData.getTableModel( + CSMWorld::UniversalId::Type_Pathgrids)); + + int parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridEdges); + + std::set >::iterator row; + for (row = rowsToRemove.begin(); row != rowsToRemove.end(); ++row) + { + commands.push(new CSMWorld::DeleteNestedCommand(*model, mId, *row, parentColumn)); + } + } + } + + osg::ref_ptr Pathgrid::getTag() const + { + return mTag; + } + + void Pathgrid::recreateGeometry() + { + mChangeGeometry = true; + } + + void Pathgrid::removeGeometry() + { + mRemoveGeometry = true; + } + + void Pathgrid::update() + { + if (mRemoveGeometry) + { + removePathgridGeometry(); + removeSelectedGeometry(); + } + else if (mChangeGeometry) + { + createGeometry(); + } + + mChangeGeometry = false; + mRemoveGeometry = false; + } + + void Pathgrid::createGeometry() + { + const CSMWorld::Pathgrid* source = getPathgridSource(); + if (source) + { + CSMWorld::Pathgrid temp; + if (mUseOffset) + { + temp = *source; + + for (NodeList::iterator it = mSelected.begin(); it != mSelected.end(); ++it) + { + temp.mPoints[*it].mX += mMoveOffset.x(); + temp.mPoints[*it].mY += mMoveOffset.y(); + temp.mPoints[*it].mZ += mMoveOffset.z(); + } + + source = &temp; + } + + removePathgridGeometry(); + mPathgridGeometry = SceneUtil::createPathgridGeometry(*source); + mPathgridGeode->addDrawable(mPathgridGeometry); + + createSelectedGeometry(*source); + } + else + { + removePathgridGeometry(); + removeSelectedGeometry(); + } + } + + void Pathgrid::createSelectedGeometry() + { + const CSMWorld::Pathgrid* source = getPathgridSource(); + if (source) + { + createSelectedGeometry(*source); + } + else + { + removeSelectedGeometry(); + } + } + + void Pathgrid::createSelectedGeometry(const CSMWorld::Pathgrid& source) + { + removeSelectedGeometry(); + + mSelectedGeometry = SceneUtil::createPathgridSelectedWireframe(source, mSelected); + mPathgridGeode->addDrawable(mSelectedGeometry); + } + + void Pathgrid::removePathgridGeometry() + { + if (mPathgridGeometry) + { + mPathgridGeode->removeDrawable(mPathgridGeometry); + mPathgridGeometry = 0; + } + } + + void Pathgrid::removeSelectedGeometry() + { + if (mSelectedGeometry) + { + mPathgridGeode->removeDrawable(mSelectedGeometry); + mSelectedGeometry = 0; + } + } + + void Pathgrid::createDragGeometry(const osg::Vec3f& start, const osg::Vec3f& end, bool valid) + { + if (mDragGeometry) + mPathgridGeode->removeDrawable(mDragGeometry); + + mDragGeometry = new osg::Geometry(); + + osg::ref_ptr vertices = new osg::Vec3Array(2); + osg::ref_ptr colors = new osg::Vec4Array(1); + osg::ref_ptr indices = new osg::DrawElementsUShort(osg::PrimitiveSet::LINES, 2); + + (*vertices)[0] = start; + (*vertices)[1] = end; + + if (valid) + { + (*colors)[0] = osg::Vec4f(0.91f, 0.66f, 1.f, 1.f); + } + else + { + (*colors)[0] = osg::Vec4f(1.f, 0.f, 0.f, 1.f); + } + + indices->setElement(0, 0); + indices->setElement(1, 1); + + mDragGeometry->setVertexArray(vertices); + mDragGeometry->setColorArray(colors, osg::Array::BIND_OVERALL); + mDragGeometry->addPrimitiveSet(indices); + mDragGeometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + + mPathgridGeode->addDrawable(mDragGeometry); + } + + const CSMWorld::Pathgrid* Pathgrid::getPathgridSource() + { + int index = mPathgridCollection.searchId(mId); + if (index != -1 && !mPathgridCollection.getRecord(index).isDeleted()) + { + return &mPathgridCollection.getRecord(index).get(); + } + + return 0; + } + + int Pathgrid::edgeExists(const CSMWorld::Pathgrid& source, unsigned short node1, unsigned short node2) + { + for (size_t i = 0; i < source.mEdges.size(); ++i) + { + if (source.mEdges[i].mV0 == node1 && source.mEdges[i].mV1 == node2) + return static_cast(i); + } + + return -1; + } + + void Pathgrid::addEdge(CSMWorld::CommandMacro& commands, const CSMWorld::Pathgrid& source, unsigned short node1, + unsigned short node2) + { + CSMWorld::IdTree* model = dynamic_cast(mData.getTableModel( + CSMWorld::UniversalId::Type_Pathgrids)); + + int recordIndex = mPathgridCollection.getIndex(mId); + int parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridEdges); + + int edge0Column = mPathgridCollection.searchNestedColumnIndex(parentColumn, + CSMWorld::Columns::ColumnId_PathgridEdge0); + + int edge1Column = mPathgridCollection.searchNestedColumnIndex(parentColumn, + CSMWorld::Columns::ColumnId_PathgridEdge1); + + QModelIndex parent = model->index(recordIndex, parentColumn); + int row = static_cast(source.mEdges.size()); + + if (edgeExists(source, node1, node2) == -1) + { + commands.push(new CSMWorld::AddNestedCommand(*model, mId, row, parentColumn)); + commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, edge0Column, parent), node1)); + commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, edge1Column, parent), node2)); + ++row; + } + + if (edgeExists(source, node2, node1) == -1) + { + commands.push(new CSMWorld::AddNestedCommand(*model, mId, row, parentColumn)); + commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, edge0Column, parent), node2)); + commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, edge1Column, parent), node1)); + } + } + + int Pathgrid::clampToCell(int v) + { + const int CellExtent = ESM::Land::REAL_SIZE; + + if (mInterior) + return v; + else if (v > CellExtent) + return CellExtent; + else if (v < 0) + return 0; + else + return v; + } +} diff --git a/apps/opencs/view/render/pathgrid.hpp b/apps/opencs/view/render/pathgrid.hpp new file mode 100644 index 0000000000..181a62b442 --- /dev/null +++ b/apps/opencs/view/render/pathgrid.hpp @@ -0,0 +1,137 @@ +#ifndef CSV_RENDER_PATHGRID_H +#define CSV_RENDER_PATHGRID_H + +#include + +#include +#include +#include + +#include "../../model/world/cellcoordinates.hpp" +#include "../../model/world/idcollection.hpp" +#include "../../model/world/subcellcollection.hpp" + +#include "tagbase.hpp" + +namespace osg +{ + class Geode; + class Geometry; + class Group; + class PositionAttitudeTransform; +} + +namespace CSMWorld +{ + class CommandMacro; + class Data; + struct Pathgrid; +} + +namespace CSVRender +{ + class Pathgrid; + + class PathgridTag : public TagBase + { + public: + + PathgridTag (Pathgrid* pathgrid); + + Pathgrid* getPathgrid () const; + + virtual QString getToolTip (bool hideBasics) const; + + private: + + Pathgrid* mPathgrid; + }; + + class Pathgrid + { + public: + + typedef std::vector NodeList; + + Pathgrid(CSMWorld::Data& data, osg::Group* parent, const std::string& pathgridId, + const CSMWorld::CellCoordinates& coordinates); + + ~Pathgrid(); + + const CSMWorld::CellCoordinates& getCoordinates() const; + const std::string& getId() const; + + bool isSelected() const; + const NodeList& getSelected() const; + void selectAll(); + void toggleSelected(unsigned short node); // Adds to end of vector + void invertSelected(); + void clearSelected(); + + void moveSelected(const osg::Vec3d& offset); + void setDragOrigin(unsigned short node); + void setDragEndpoint(unsigned short node); + void setDragEndpoint(const osg::Vec3d& pos); + + void resetIndicators(); + + void applyPoint(CSMWorld::CommandMacro& commands, const osg::Vec3d& worldPos); + void applyPosition(CSMWorld::CommandMacro& commands); + void applyEdge(CSMWorld::CommandMacro& commands, unsigned short node1, unsigned short node2); + void applyEdges(CSMWorld::CommandMacro& commands, unsigned short node); + void applyRemoveNodes(CSMWorld::CommandMacro& commands); + void applyRemoveEdges(CSMWorld::CommandMacro& commands); + + osg::ref_ptr getTag() const; + + void recreateGeometry(); + void removeGeometry(); + + void update(); + + private: + + CSMWorld::Data& mData; + CSMWorld::SubCellCollection& mPathgridCollection; + std::string mId; + CSMWorld::CellCoordinates mCoords; + bool mInterior; + + NodeList mSelected; + osg::Vec3d mMoveOffset; + unsigned short mDragOrigin; + + bool mChangeGeometry; + bool mRemoveGeometry; + bool mUseOffset; + + osg::Group* mParent; + osg::ref_ptr mBaseNode; + osg::ref_ptr mPathgridGeode; + osg::ref_ptr mPathgridGeometry; + osg::ref_ptr mSelectedGeometry; + osg::ref_ptr mDragGeometry; + + osg::ref_ptr mTag; + + void createGeometry(); + void createSelectedGeometry(); + void createSelectedGeometry(const CSMWorld::Pathgrid& source); + void removePathgridGeometry(); + void removeSelectedGeometry(); + + void createDragGeometry(const osg::Vec3f& start, const osg::Vec3f& end, bool valid); + + const CSMWorld::Pathgrid* getPathgridSource(); + + int edgeExists(const CSMWorld::Pathgrid& source, unsigned short node1, unsigned short node2); + void addEdge(CSMWorld::CommandMacro& commands, const CSMWorld::Pathgrid& source, unsigned short node1, + unsigned short node2); + void removeEdge(CSMWorld::CommandMacro& commands, const CSMWorld::Pathgrid& source, unsigned short node1, + unsigned short node2); + + int clampToCell(int v); + }; +} + +#endif diff --git a/apps/opencs/view/render/pathgridmode.cpp b/apps/opencs/view/render/pathgridmode.cpp new file mode 100644 index 0000000000..87ec805564 --- /dev/null +++ b/apps/opencs/view/render/pathgridmode.cpp @@ -0,0 +1,265 @@ +#include "pathgridmode.hpp" + +#include +#include + +#include + +#include "../../model/prefs/state.hpp" + +#include "../../model/world/commands.hpp" +#include "../../model/world/commandmacro.hpp" +#include "../../model/world/idtable.hpp" +#include "../../model/world/idtree.hpp" + +#include "../widget/scenetoolbar.hpp" + +#include "cell.hpp" +#include "mask.hpp" +#include "pathgrid.hpp" +#include "pathgridselectionmode.hpp" +#include "worldspacewidget.hpp" + +namespace CSVRender +{ + PathgridMode::PathgridMode(WorldspaceWidget* worldspaceWidget, QWidget* parent) + : EditMode(worldspaceWidget, QIcon(":placeholder"), Mask_Pathgrid | Mask_Terrain | Mask_Reference, + getTooltip(), parent) + , mDragMode(DragMode_None) + , mFromNode(0) + , mSelectionMode(0) + { + } + + QString PathgridMode::getTooltip() + { + return QString( + "Pathgrid editing" + "
  • Primary edit: Add node to scene
  • " + "
  • Secondary edit: Connect selected nodes to node
  • " + "
  • Primary drag: Move selected nodes
  • " + "
  • Secondary drag: Connect one node to another
  • " + "

Note: Only a single cell's pathgrid may be edited at a time"); + } + + void PathgridMode::activate(CSVWidget::SceneToolbar* toolbar) + { + if (!mSelectionMode) + { + mSelectionMode = new PathgridSelectionMode(toolbar, getWorldspaceWidget()); + } + + EditMode::activate(toolbar); + toolbar->addTool(mSelectionMode); + } + + void PathgridMode::primaryEditPressed(const WorldspaceHitResult& hitResult) + { + if (CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue() && + dynamic_cast(hitResult.tag.get())) + { + primarySelectPressed(hitResult); + } + else if (Cell* cell = getWorldspaceWidget().getCell (hitResult.worldPos)) + { + // Add node + QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); + QString description = "Add node"; + + CSMWorld::CommandMacro macro(undoStack, description); + cell->getPathgrid()->applyPoint(macro, hitResult.worldPos); + } + } + + void PathgridMode::secondaryEditPressed(const WorldspaceHitResult& hit) + { + if (hit.tag) + { + if (PathgridTag* tag = dynamic_cast(hit.tag.get())) + { + if (tag->getPathgrid()->isSelected()) + { + unsigned short node = SceneUtil::getPathgridNode(static_cast(hit.index0)); + + QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); + QString description = "Connect node to selected nodes"; + + CSMWorld::CommandMacro macro(undoStack, description); + tag->getPathgrid()->applyEdges(macro, node); + } + } + } + } + + void PathgridMode::primarySelectPressed(const WorldspaceHitResult& hit) + { + getWorldspaceWidget().clearSelection(Mask_Pathgrid); + + if (hit.tag) + { + if (PathgridTag* tag = dynamic_cast(hit.tag.get())) + { + mLastId = tag->getPathgrid()->getId(); + unsigned short node = SceneUtil::getPathgridNode(static_cast(hit.index0)); + tag->getPathgrid()->toggleSelected(node); + } + } + } + + void PathgridMode::secondarySelectPressed(const WorldspaceHitResult& hit) + { + if (hit.tag) + { + if (PathgridTag* tag = dynamic_cast(hit.tag.get())) + { + if (tag->getPathgrid()->getId() != mLastId) + { + getWorldspaceWidget().clearSelection(Mask_Pathgrid); + mLastId = tag->getPathgrid()->getId(); + } + + unsigned short node = SceneUtil::getPathgridNode(static_cast(hit.index0)); + tag->getPathgrid()->toggleSelected(node); + + return; + } + } + + getWorldspaceWidget().clearSelection(Mask_Pathgrid); + } + + bool PathgridMode::primaryEditStartDrag(const QPoint& pos) + { + std::vector > selection = getWorldspaceWidget().getSelection (Mask_Pathgrid); + + if (CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) + { + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + + if (dynamic_cast(hit.tag.get())) + { + primarySelectPressed(hit); + selection = getWorldspaceWidget().getSelection (Mask_Pathgrid); + } + } + + if (!selection.empty()) + { + mDragMode = DragMode_Move; + return true; + } + + return false; + } + + bool PathgridMode::secondaryEditStartDrag(const QPoint& pos) + { + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + if (hit.tag) + { + if (PathgridTag* tag = dynamic_cast(hit.tag.get())) + { + mDragMode = DragMode_Edge; + mEdgeId = tag->getPathgrid()->getId(); + mFromNode = SceneUtil::getPathgridNode(static_cast(hit.index0)); + + tag->getPathgrid()->setDragOrigin(mFromNode); + return true; + } + } + + return false; + } + + void PathgridMode::drag(const QPoint& pos, int diffX, int diffY, double speedFactor) + { + if (mDragMode == DragMode_Move) + { + std::vector > selection = getWorldspaceWidget().getSelection(Mask_Pathgrid); + + for (std::vector >::iterator it = selection.begin(); it != selection.end(); ++it) + { + if (PathgridTag* tag = dynamic_cast(it->get())) + { + osg::Vec3d eye, center, up, offset; + getWorldspaceWidget().getCamera()->getViewMatrix().getLookAt (eye, center, up); + + offset = (up * diffY * speedFactor) + (((center - eye) ^ up) * diffX * speedFactor); + + tag->getPathgrid()->moveSelected(offset); + } + } + } + else if (mDragMode == DragMode_Edge) + { + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + + Cell* cell = getWorldspaceWidget().getCell(hit.worldPos); + if (cell) + { + PathgridTag* tag = 0; + if (hit.tag && (tag = dynamic_cast(hit.tag.get())) && tag->getPathgrid()->getId() == mEdgeId) + { + unsigned short node = SceneUtil::getPathgridNode(static_cast(hit.index0)); + cell->getPathgrid()->setDragEndpoint(node); + } + else + { + cell->getPathgrid()->setDragEndpoint(hit.worldPos); + } + + } + } + } + + void PathgridMode::dragCompleted(const QPoint& pos) + { + if (mDragMode == DragMode_Move) + { + std::vector > selection = getWorldspaceWidget().getSelection (Mask_Pathgrid); + for (std::vector >::iterator it = selection.begin(); it != selection.end(); ++it) + { + if (PathgridTag* tag = dynamic_cast(it->get())) + { + QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); + QString description = "Move pathgrid node(s)"; + + CSMWorld::CommandMacro macro(undoStack, description); + tag->getPathgrid()->applyPosition(macro); + } + } + } + else if (mDragMode == DragMode_Edge) + { + WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); + + if (hit.tag) + { + if (PathgridTag* tag = dynamic_cast(hit.tag.get())) + { + if (tag->getPathgrid()->getId() == mEdgeId) + { + unsigned short toNode = SceneUtil::getPathgridNode(static_cast(hit.index0)); + + QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); + QString description = "Add edge between nodes"; + + CSMWorld::CommandMacro macro(undoStack, description); + tag->getPathgrid()->applyEdge(macro, mFromNode, toNode); + } + } + } + + mEdgeId.clear(); + mFromNode = 0; + } + + mDragMode = DragMode_None; + getWorldspaceWidget().reset(Mask_Pathgrid); + } + + void PathgridMode::dragAborted() + { + getWorldspaceWidget().reset(Mask_Pathgrid); + } +} diff --git a/apps/opencs/view/render/pathgridmode.hpp b/apps/opencs/view/render/pathgridmode.hpp new file mode 100644 index 0000000000..908eefa5b2 --- /dev/null +++ b/apps/opencs/view/render/pathgridmode.hpp @@ -0,0 +1,61 @@ +#ifndef CSV_RENDER_PATHGRIDMODE_H +#define CSV_RENDER_PATHGRIDMODE_H + +#include + +#include "editmode.hpp" + +namespace CSVRender +{ + class PathgridSelectionMode; + + class PathgridMode : public EditMode + { + Q_OBJECT + + public: + + PathgridMode(WorldspaceWidget* worldspace, QWidget* parent=0); + + virtual void activate(CSVWidget::SceneToolbar* toolbar); + + virtual void primaryEditPressed(const WorldspaceHitResult& hit); + + virtual void secondaryEditPressed(const WorldspaceHitResult& hit); + + virtual void primarySelectPressed(const WorldspaceHitResult& hit); + + virtual void secondarySelectPressed(const WorldspaceHitResult& hit); + + virtual bool primaryEditStartDrag (const QPoint& pos); + + virtual bool secondaryEditStartDrag (const QPoint& pos); + + virtual void drag (const QPoint& pos, int diffX, int diffY, double speedFactor); + + virtual void dragCompleted(const QPoint& pos); + + /// \note dragAborted will not be called, if the drag is aborted via changing + /// editing mode + virtual void dragAborted(); + + private: + + enum DragMode + { + DragMode_None, + DragMode_Move, + DragMode_Edge + }; + + DragMode mDragMode; + std::string mLastId, mEdgeId; + unsigned short mFromNode; + + PathgridSelectionMode* mSelectionMode; + + QString getTooltip(); + }; +} + +#endif diff --git a/apps/opencs/view/render/pathgridselectionmode.cpp b/apps/opencs/view/render/pathgridselectionmode.cpp new file mode 100644 index 0000000000..db41faf50d --- /dev/null +++ b/apps/opencs/view/render/pathgridselectionmode.cpp @@ -0,0 +1,71 @@ +#include "pathgridselectionmode.hpp" + +#include +#include + +#include "../../model/world/idtable.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/commandmacro.hpp" + +#include "worldspacewidget.hpp" +#include "pathgrid.hpp" + +namespace CSVRender +{ + PathgridSelectionMode::PathgridSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget) + : SelectionMode(parent, worldspaceWidget, Mask_Pathgrid) + { + mRemoveSelectedNodes = new QAction("Remove selected nodes", this); + mRemoveSelectedEdges = new QAction("Remove edges between selected nodes", this); + + connect(mRemoveSelectedNodes, SIGNAL(triggered()), this, SLOT(removeSelectedNodes())); + connect(mRemoveSelectedEdges, SIGNAL(triggered()), this, SLOT(removeSelectedEdges())); + } + + bool PathgridSelectionMode::createContextMenu(QMenu* menu) + { + if (menu) + { + SelectionMode::createContextMenu(menu); + + menu->addAction(mRemoveSelectedNodes); + menu->addAction(mRemoveSelectedEdges); + } + + return true; + } + + void PathgridSelectionMode::removeSelectedNodes() + { + std::vector > selection = getWorldspaceWidget().getSelection (Mask_Pathgrid); + + for (std::vector >::iterator it = selection.begin(); it != selection.end(); ++it) + { + if (PathgridTag* tag = dynamic_cast(it->get())) + { + QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); + QString description = "Remove selected nodes"; + + CSMWorld::CommandMacro macro(undoStack, description); + tag->getPathgrid()->applyRemoveNodes(macro); + } + } + } + + void PathgridSelectionMode::removeSelectedEdges() + { + std::vector > selection = getWorldspaceWidget().getSelection (Mask_Pathgrid); + + for (std::vector >::iterator it = selection.begin(); it != selection.end(); ++it) + { + if (PathgridTag* tag = dynamic_cast(it->get())) + { + QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); + QString description = "Remove edges between selected nodes"; + + CSMWorld::CommandMacro macro(undoStack, description); + tag->getPathgrid()->applyRemoveEdges(macro); + } + } + } +} diff --git a/apps/opencs/view/render/pathgridselectionmode.hpp b/apps/opencs/view/render/pathgridselectionmode.hpp new file mode 100644 index 0000000000..e4cb1e0440 --- /dev/null +++ b/apps/opencs/view/render/pathgridselectionmode.hpp @@ -0,0 +1,38 @@ +#ifndef CSV_RENDER_PATHGRID_SELECTION_MODE_H +#define CSV_RENDER_PATHGRID_SELECTION_MODE_H + +#include "selectionmode.hpp" + +namespace CSVRender +{ + class PathgridSelectionMode : public SelectionMode + { + Q_OBJECT + + public: + + PathgridSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget); + + protected: + + /// Add context menu items to \a menu. + /// + /// \attention menu can be a 0-pointer + /// + /// \return Have there been any menu items to be added (if menu is 0 and there + /// items to be added, the function must return true anyway. + bool createContextMenu(QMenu* menu); + + private: + + QAction* mRemoveSelectedNodes; + QAction* mRemoveSelectedEdges; + + private slots: + + void removeSelectedNodes(); + void removeSelectedEdges(); + }; +} + +#endif diff --git a/apps/opencs/view/render/previewwidget.cpp b/apps/opencs/view/render/previewwidget.cpp index a03b277d30..2f35103171 100644 --- a/apps/opencs/view/render/previewwidget.cpp +++ b/apps/opencs/view/render/previewwidget.cpp @@ -1,7 +1,5 @@ #include "previewwidget.hpp" -#include - #include "../../model/world/data.hpp" #include "../../model/world/idtable.hpp" @@ -9,7 +7,7 @@ CSVRender::PreviewWidget::PreviewWidget (CSMWorld::Data& data, const std::string& id, bool referenceable, QWidget *parent) : SceneWidget (data.getResourceSystem(), parent), mData (data), mObject(data, mRootNode, id, referenceable) { - mView->setCameraManipulator(new osgGA::TrackballManipulator); + selectNavigationMode("orbit"); QAbstractItemModel *referenceables = mData.getTableModel (CSMWorld::UniversalId::Type_Referenceables); diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index b1300a991f..b2b5b30d63 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include #include @@ -14,10 +14,15 @@ #include #include +#include #include "../widget/scenetoolmode.hpp" +#include "../../model/prefs/state.hpp" + #include "lighting.hpp" +#include "mask.hpp" +#include "cameracontroller.hpp" namespace CSVRender { @@ -55,15 +60,15 @@ RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f) layout->addWidget(window->getGLWidget()); setLayout(layout); - // Pass events through this widget first - window->getGLWidget()->installEventFilter(this); - mView->getCamera()->setGraphicsContext(window); mView->getCamera()->setClearColor( osg::Vec4(0.2, 0.2, 0.6, 1.0) ); mView->getCamera()->setViewport( new osg::Viewport(0, 0, traits->width, traits->height) ); mView->getCamera()->setProjectionMatrixAsPerspective(30.0f, static_cast(traits->width)/static_cast(traits->height), 1.0f, 10000.0f ); - mRootNode = new osg::Group; + SceneUtil::LightManager* lightMgr = new SceneUtil::LightManager; + lightMgr->setStartLight(1); + lightMgr->setLightingMask(Mask_Lighting); + mRootNode = lightMgr; mView->getCamera()->getOrCreateStateSet()->setMode(GL_NORMALIZE, osg::StateAttribute::ON); mView->getCamera()->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::ON); @@ -73,7 +78,7 @@ RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f) // Press S to reveal profiling stats mView->addEventHandler(new osgViewer::StatsHandler); - mView->getCamera()->setCullMask(~(0x1)); + mView->getCamera()->setCullMask(~(Mask_UpdateVisitor)); viewer.addView(mView); viewer.setDone(false); @@ -92,30 +97,15 @@ void RenderWidget::flagAsModified() void RenderWidget::setVisibilityMask(int mask) { - // 0x1 reserved for separating cull and update visitors - mView->getCamera()->setCullMask(mask<<1); + mView->getCamera()->setCullMask(mask | Mask_ParticleSystem | Mask_Lighting); } -bool RenderWidget::eventFilter(QObject* obj, QEvent* event) +osg::Camera *RenderWidget::getCamera() { - // handle event in this widget, is there a better way to do this? - if (event->type() == QEvent::MouseButtonPress) - mousePressEvent(static_cast(event)); - if (event->type() == QEvent::MouseButtonRelease) - mouseReleaseEvent(static_cast(event)); - if (event->type() == QEvent::MouseMove) - mouseMoveEvent(static_cast(event)); - if (event->type() == QEvent::KeyPress) - keyPressEvent(static_cast(event)); - if (event->type() == QEvent::KeyRelease) - keyReleaseEvent(static_cast(event)); - if (event->type() == QEvent::Wheel) - wheelEvent(static_cast(event)); - - // Always pass the event on to GLWidget, i.e. to OSG event queue - return QObject::eventFilter(obj, event); + return mView->getCamera(); } + // -------------------------------------------------- CompositeViewer::CompositeViewer() @@ -149,27 +139,59 @@ CompositeViewer &CompositeViewer::get() void CompositeViewer::update() { - mSimulationTime += mFrameTimer.time_s(); + double dt = mFrameTimer.time_s(); mFrameTimer.setStartTick(); + + emit simulationUpdated(dt); + + mSimulationTime += dt; frame(mSimulationTime); } // --------------------------------------------------- -SceneWidget::SceneWidget(boost::shared_ptr resourceSystem, QWidget *parent, Qt::WindowFlags f) +SceneWidget::SceneWidget(boost::shared_ptr resourceSystem, QWidget *parent, Qt::WindowFlags f, + bool retrieveInput) : RenderWidget(parent, f) , mResourceSystem(resourceSystem) , mLighting(NULL) , mHasDefaultAmbient(false) + , mPrevMouseX(0) + , mPrevMouseY(0) + , mFreeCamControl(new FreeCameraController()) + , mOrbitCamControl(new OrbitCameraController()) + , mCurrentCamControl(mFreeCamControl.get()) + , mCamPositionSet(false) { + mOrbitCamControl->setPickingMask(Mask_Reference | Mask_Terrain); + selectNavigationMode("free"); + // we handle lighting manually mView->setLightingMode(osgViewer::View::NO_LIGHT); setLighting(&mLightingDay); + mResourceSystem->getSceneManager()->setParticleSystemMask(Mask_ParticleSystem); + + // Recieve mouse move event even if mouse button is not pressed + setMouseTracking(true); + setFocusPolicy(Qt::StrongFocus); + /// \todo make shortcut configurable QShortcut *focusToolbar = new QShortcut (Qt::Key_T, this, 0, 0, Qt::WidgetWithChildrenShortcut); connect (focusToolbar, SIGNAL (activated()), this, SIGNAL (focusToolbarRequest())); + + connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), + this, SLOT (settingChanged (const CSMPrefs::Setting *))); + + // TODO update this outside of the constructor where virtual methods can be used + if (retrieveInput) + { + CSMPrefs::get()["3D Scene Input"].update(); + CSMPrefs::get()["Tooltips"].update(); + } + + connect (&CompositeViewer::get(), SIGNAL (simulationUpdated(double)), this, SLOT (update(double))); } SceneWidget::~SceneWidget() @@ -249,4 +271,186 @@ void SceneWidget::setDefaultAmbient (const osg::Vec4f& colour) setAmbient(mLighting->getAmbientColour(&mDefaultAmbient)); } +void SceneWidget::mousePressEvent (QMouseEvent *event) +{ + mMouseMode = mapButton(event); + + mPrevMouseX = event->x(); + mPrevMouseY = event->y(); +} + +void SceneWidget::mouseReleaseEvent (QMouseEvent *event) +{ + mMouseMode = ""; +} + +void SceneWidget::mouseMoveEvent (QMouseEvent *event) +{ + mCurrentCamControl->handleMouseMoveEvent(mMouseMode, event->x() - mPrevMouseX, event->y() - mPrevMouseY); + + mPrevMouseX = event->x(); + mPrevMouseY = event->y(); +} + +void SceneWidget::focusOutEvent (QFocusEvent *event) +{ + mCurrentCamControl->resetInput(); +} + +void SceneWidget::wheelEvent(QWheelEvent *event) +{ + mCurrentCamControl->handleMouseMoveEvent("t-navi", event->delta(), 0); +} + +void SceneWidget::keyPressEvent (QKeyEvent *event) +{ + mCurrentCamControl->handleKeyEvent(event, true); +} + +void SceneWidget::keyReleaseEvent (QKeyEvent *event) +{ + mCurrentCamControl->handleKeyEvent(event, false); +} + +void SceneWidget::update(double dt) +{ + if (mCamPositionSet) + { + mCurrentCamControl->update(dt); + } + else + { + mCurrentCamControl->setup(mRootNode, Mask_Reference | Mask_Terrain, CameraController::WorldUp); + mCamPositionSet = true; + } +} + +void SceneWidget::settingChanged (const CSMPrefs::Setting *setting) +{ + if (*setting=="3D Scene Input/p-navi-free-sensitivity") + { + mFreeCamControl->setCameraSensitivity(setting->toDouble()); + } + else if (*setting=="3D Scene Input/p-navi-orbit-sensitivity") + { + mOrbitCamControl->setCameraSensitivity(setting->toDouble()); + } + else if (*setting=="3D Scene Input/p-navi-free-invert") + { + mFreeCamControl->setInverted(setting->isTrue()); + } + else if (*setting=="3D Scene Input/p-navi-orbit-invert") + { + mOrbitCamControl->setInverted(setting->isTrue()); + } + else if (*setting=="3D Scene Input/s-navi-sensitivity") + { + mFreeCamControl->setSecondaryMovementMultiplier(setting->toDouble()); + mOrbitCamControl->setSecondaryMovementMultiplier(setting->toDouble()); + } + else if (*setting=="3D Scene Input/navi-wheel-factor") + { + mFreeCamControl->setWheelMovementMultiplier(setting->toDouble()); + mOrbitCamControl->setWheelMovementMultiplier(setting->toDouble()); + } + else if (*setting=="3D Scene Input/navi-free-lin-speed") + { + mFreeCamControl->setLinearSpeed(setting->toDouble()); + } + else if (*setting=="3D Scene Input/navi-free-rot-speed") + { + mFreeCamControl->setRotationalSpeed(setting->toDouble()); + } + else if (*setting=="3D Scene Input/navi-free-speed-mult") + { + mFreeCamControl->setSpeedMultiplier(setting->toDouble()); + } + else if (*setting=="3D Scene Input/navi-orbit-rot-speed") + { + mOrbitCamControl->setOrbitSpeed(setting->toDouble()); + } + else if (*setting=="3D Scene Input/navi-orbit-speed-mult") + { + mOrbitCamControl->setOrbitSpeedMultiplier(setting->toDouble()); + } + else + { + storeMappingSetting(setting); + } +} + +void SceneWidget::selectNavigationMode (const std::string& mode) +{ + if (mode=="1st") + { + mCurrentCamControl->setCamera(NULL); + mCurrentCamControl = mFreeCamControl.get(); + mCurrentCamControl->setCamera(getCamera()); + mFreeCamControl->fixUpAxis(CameraController::WorldUp); + } + else if (mode=="free") + { + mCurrentCamControl->setCamera(NULL); + mCurrentCamControl = mFreeCamControl.get(); + mCurrentCamControl->setCamera(getCamera()); + mFreeCamControl->unfixUpAxis(); + } + else if (mode=="orbit") + { + mCurrentCamControl->setCamera(NULL); + mCurrentCamControl = mOrbitCamControl.get(); + mCurrentCamControl->setCamera(getCamera()); + } +} + +bool SceneWidget::storeMappingSetting (const CSMPrefs::Setting *setting) +{ + if (setting->getParent()->getKey()!="3D Scene Input") + return false; + + static const char * const sMappingSettings[] = + { + "p-navi", "s-navi", + 0 + }; + + for (int i=0; sMappingSettings[i]; ++i) + if (setting->getKey()==sMappingSettings[i]) + { + QString value = QString::fromUtf8 (setting->toString().c_str()); + + Qt::MouseButton button = Qt::NoButton; + + if (value.endsWith ("Left Mouse-Button")) + button = Qt::LeftButton; + else if (value.endsWith ("Right Mouse-Button")) + button = Qt::RightButton; + else if (value.endsWith ("Middle Mouse-Button")) + button = Qt::MiddleButton; + else + return false; + + bool ctrl = value.startsWith ("Ctrl-"); + + mButtonMapping[std::make_pair (button, ctrl)] = sMappingSettings[i]; + return true; + } + + return false; +} + +std::string SceneWidget::mapButton (QMouseEvent *event) +{ + std::pair phyiscal ( + event->button(), event->modifiers() & Qt::ControlModifier); + + std::map, std::string>::const_iterator iter = + mButtonMapping.find (phyiscal); + + if (iter!=mButtonMapping.end()) + return iter->second; + + return ""; +} + } diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp index 63da01a45b..4df49543a3 100644 --- a/apps/opencs/view/render/scenewidget.hpp +++ b/apps/opencs/view/render/scenewidget.hpp @@ -1,6 +1,9 @@ #ifndef OPENCS_VIEW_SCENEWIDGET_H #define OPENCS_VIEW_SCENEWIDGET_H +#include +#include + #include #include @@ -21,6 +24,7 @@ namespace Resource namespace osg { class Group; + class Camera; } namespace CSVWidget @@ -29,91 +33,136 @@ namespace CSVWidget class SceneToolbar; } +namespace CSMPrefs +{ + class Setting; +} + namespace CSVRender { + class CameraController; + class FreeCameraController; + class OrbitCameraController; class Lighting; class RenderWidget : public QWidget { - Q_OBJECT + Q_OBJECT - public: - RenderWidget(QWidget* parent = 0, Qt::WindowFlags f = 0); - virtual ~RenderWidget(); + public: + RenderWidget(QWidget* parent = 0, Qt::WindowFlags f = 0); + virtual ~RenderWidget(); - void flagAsModified(); + void flagAsModified(); - void setVisibilityMask(int mask); + void setVisibilityMask(int mask); - bool eventFilter(QObject *, QEvent *); + osg::Camera *getCamera(); - protected: + protected: - osg::ref_ptr mView; + osg::ref_ptr mView; - osg::Group* mRootNode; + osg::Group* mRootNode; - QTimer mTimer; + QTimer mTimer; }; // Extension of RenderWidget to support lighting mode selection & toolbar class SceneWidget : public RenderWidget { - Q_OBJECT - public: - SceneWidget(boost::shared_ptr resourceSystem, QWidget* parent = 0, Qt::WindowFlags f = 0); - virtual ~SceneWidget(); + Q_OBJECT + public: + SceneWidget(boost::shared_ptr resourceSystem, QWidget* parent = 0, + Qt::WindowFlags f = 0, bool retrieveInput = true); + virtual ~SceneWidget(); - CSVWidget::SceneToolMode *makeLightingSelector (CSVWidget::SceneToolbar *parent); - ///< \attention The created tool is not added to the toolbar (via addTool). Doing that - /// is the responsibility of the calling function. + CSVWidget::SceneToolMode *makeLightingSelector (CSVWidget::SceneToolbar *parent); + ///< \attention The created tool is not added to the toolbar (via addTool). Doing that + /// is the responsibility of the calling function. - void setDefaultAmbient (const osg::Vec4f& colour); - ///< \note The actual ambient colour may differ based on lighting settings. + void setDefaultAmbient (const osg::Vec4f& colour); + ///< \note The actual ambient colour may differ based on lighting settings. - protected: - void setLighting (Lighting *lighting); - ///< \attention The ownership of \a lighting is not transferred to *this. + protected: + void setLighting (Lighting *lighting); + ///< \attention The ownership of \a lighting is not transferred to *this. - void setAmbient(const osg::Vec4f& ambient); + void setAmbient(const osg::Vec4f& ambient); - boost::shared_ptr mResourceSystem; + virtual void mousePressEvent (QMouseEvent *event); + virtual void mouseReleaseEvent (QMouseEvent *event); + virtual void mouseMoveEvent (QMouseEvent *event); + virtual void wheelEvent (QWheelEvent *event); + virtual void keyPressEvent (QKeyEvent *event); + virtual void keyReleaseEvent (QKeyEvent *event); + virtual void focusOutEvent (QFocusEvent *event); - Lighting* mLighting; + /// \return Is \a key a button mapping setting? (ignored otherwise) + virtual bool storeMappingSetting (const CSMPrefs::Setting *setting); - osg::Vec4f mDefaultAmbient; - bool mHasDefaultAmbient; - LightingDay mLightingDay; - LightingNight mLightingNight; - LightingBright mLightingBright; + std::string mapButton (QMouseEvent *event); - private slots: + boost::shared_ptr mResourceSystem; - void selectLightingMode (const std::string& mode); + Lighting* mLighting; + + osg::Vec4f mDefaultAmbient; + bool mHasDefaultAmbient; + LightingDay mLightingDay; + LightingNight mLightingNight; + LightingBright mLightingBright; + + int mPrevMouseX, mPrevMouseY; + std::string mMouseMode; + std::auto_ptr mFreeCamControl; + std::auto_ptr mOrbitCamControl; + CameraController* mCurrentCamControl; + + std::map, std::string> mButtonMapping; + + private: + bool mCamPositionSet; + + public slots: + void update(double dt); + + protected slots: + + virtual void settingChanged (const CSMPrefs::Setting *setting); + + void selectNavigationMode (const std::string& mode); + + private slots: + + void selectLightingMode (const std::string& mode); signals: - void focusToolbarRequest(); + void focusToolbarRequest(); }; // There are rendering glitches when using multiple Viewer instances, work around using CompositeViewer with multiple views class CompositeViewer : public QObject, public osgViewer::CompositeViewer { - Q_OBJECT - public: - CompositeViewer(); + Q_OBJECT + public: + CompositeViewer(); - static CompositeViewer& get(); + static CompositeViewer& get(); - QTimer mTimer; + QTimer mTimer; - private: - osg::Timer mFrameTimer; - double mSimulationTime; + private: + osg::Timer mFrameTimer; + double mSimulationTime; - public slots: - void update(); + public slots: + void update(); + + signals: + void simulationUpdated(double dt); }; } diff --git a/apps/opencs/view/render/selectionmode.cpp b/apps/opencs/view/render/selectionmode.cpp new file mode 100644 index 0000000000..82a3c49e45 --- /dev/null +++ b/apps/opencs/view/render/selectionmode.cpp @@ -0,0 +1,77 @@ +#include "selectionmode.hpp" + +#include +#include + +#include "worldspacewidget.hpp" + +namespace CSVRender +{ + SelectionMode::SelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget, + unsigned int interactionMask) + : SceneToolMode(parent, "Selection mode") + , mWorldspaceWidget(worldspaceWidget) + , mInteractionMask(interactionMask) + { + addButton(":placeholder", "cube-centre", + "Centred cube" + "

  • Drag with primary (make instances the selection) or secondary (invert selection state) select button from the centre of the selection cube outwards
  • " + "
  • The selection cube is aligned to the word space axis
  • " + "
  • If context selection mode is enabled, a drag with primary/secondary edit not starting on an instance will have the same effect
  • " + "
" + "Not implemented yet"); + addButton(":placeholder", "cube-corner", + "Cube corner to corner" + "
  • Drag with primary (make instances the selection) or secondary (invert selection state) select button from one corner of the selection cube to the opposite corner
  • " + "
  • The selection cube is aligned to the word space axis
  • " + "
  • If context selection mode is enabled, a drag with primary/secondary edit not starting on an instance will have the same effect
  • " + "
" + "Not implemented yet"); + addButton(":placeholder", "sphere", + "Centred sphere" + "
  • Drag with primary (make instances the selection) or secondary (invert selection state) select button from the centre of the selection sphere outwards
  • " + "
  • If context selection mode is enabled, a drag with primary/secondary edit not starting on an instance will have the same effect
  • " + "
" + "Not implemented yet"); + + mSelectAll = new QAction("Select all", this); + mDeselectAll = new QAction("Clear selection", this); + mInvertSelection = new QAction("Invert selection", this); + + connect(mSelectAll, SIGNAL(triggered()), this, SLOT(selectAll())); + connect(mDeselectAll, SIGNAL(triggered()), this, SLOT(clearSelection())); + connect(mInvertSelection, SIGNAL(triggered()), this, SLOT(invertSelection())); + } + + WorldspaceWidget& SelectionMode::getWorldspaceWidget() + { + return mWorldspaceWidget; + } + + bool SelectionMode::createContextMenu (QMenu* menu) + { + if (menu) + { + menu->addAction(mSelectAll); + menu->addAction(mDeselectAll); + menu->addAction(mInvertSelection); + } + + return true; + } + + void SelectionMode::selectAll() + { + getWorldspaceWidget().selectAll(mInteractionMask); + } + + void SelectionMode::clearSelection() + { + getWorldspaceWidget().clearSelection(mInteractionMask); + } + + void SelectionMode::invertSelection() + { + getWorldspaceWidget().invertSelection(mInteractionMask); + } +} diff --git a/apps/opencs/view/render/selectionmode.hpp b/apps/opencs/view/render/selectionmode.hpp new file mode 100644 index 0000000000..f28888bfde --- /dev/null +++ b/apps/opencs/view/render/selectionmode.hpp @@ -0,0 +1,51 @@ +#ifndef CSV_RENDER_SELECTION_MODE_H +#define CSV_RENDER_SELECTION_MODE_H + +#include "../widget/scenetoolmode.hpp" + +#include "mask.hpp" + +class QAction; + +namespace CSVRender +{ + class WorldspaceWidget; + + class SelectionMode : public CSVWidget::SceneToolMode + { + Q_OBJECT + + public: + + SelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget, + unsigned int interactionMask); + + protected: + + WorldspaceWidget& getWorldspaceWidget(); + + /// Add context menu items to \a menu. + /// + /// \attention menu can be a 0-pointer + /// + /// \return Have there been any menu items to be added (if menu is 0 and there + /// items to be added, the function must return true anyway. + virtual bool createContextMenu (QMenu* menu); + + private: + + WorldspaceWidget& mWorldspaceWidget; + unsigned int mInteractionMask; + QAction* mSelectAll; + QAction* mDeselectAll; + QAction* mInvertSelection; + + protected slots: + + virtual void selectAll(); + virtual void clearSelection(); + virtual void invertSelection(); + }; +} + +#endif diff --git a/apps/opencs/view/render/tagbase.cpp b/apps/opencs/view/render/tagbase.cpp index 79412c1321..3ddd68690f 100644 --- a/apps/opencs/view/render/tagbase.cpp +++ b/apps/opencs/view/render/tagbase.cpp @@ -1,11 +1,11 @@ #include "tagbase.hpp" -CSVRender::TagBase::TagBase (Elements element) : mElement (element) {} +CSVRender::TagBase::TagBase (Mask mask) : mMask (mask) {} -CSVRender::Elements CSVRender::TagBase::getElement() const +CSVRender::Mask CSVRender::TagBase::getMask() const { - return mElement; + return mMask; } QString CSVRender::TagBase::getToolTip (bool hideBasics) const diff --git a/apps/opencs/view/render/tagbase.hpp b/apps/opencs/view/render/tagbase.hpp index 9f169c3b1a..d1ecd2cfd9 100644 --- a/apps/opencs/view/render/tagbase.hpp +++ b/apps/opencs/view/render/tagbase.hpp @@ -5,19 +5,19 @@ #include -#include "elements.hpp" +#include "mask.hpp" namespace CSVRender { class TagBase : public osg::Referenced { - Elements mElement; + Mask mMask; public: - TagBase (Elements element); + TagBase (Mask mask); - Elements getElement() const; + Mask getMask() const; virtual QString getToolTip (bool hideBasics) const; diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index 68f068daca..d9a85fe352 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -2,8 +2,6 @@ #include -#include - #include #include @@ -17,7 +15,7 @@ #include "../widget/scenetooltoggle.hpp" #include "../widget/scenetooltoggle2.hpp" -#include "elements.hpp" +#include "mask.hpp" void CSVRender::UnpagedWorldspaceWidget::update() { @@ -34,7 +32,7 @@ void CSVRender::UnpagedWorldspaceWidget::update() } CSVRender::UnpagedWorldspaceWidget::UnpagedWorldspaceWidget (const std::string& cellId, CSMDoc::Document& document, QWidget* parent) -: WorldspaceWidget (document, parent), mCellId (cellId) +: WorldspaceWidget (document, parent), mDocument(document), mCellId (cellId) { mCellsModel = &dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); @@ -50,8 +48,6 @@ CSVRender::UnpagedWorldspaceWidget::UnpagedWorldspaceWidget (const std::string& update(); mCell.reset (new Cell (document.getData(), mRootNode, mCellId)); - - mView->setCameraManipulator(new osgGA::TrackballManipulator); } void CSVRender::UnpagedWorldspaceWidget::cellDataChanged (const QModelIndex& topLeft, @@ -108,6 +104,56 @@ void CSVRender::UnpagedWorldspaceWidget::clearSelection (int elementMask) flagAsModified(); } +void CSVRender::UnpagedWorldspaceWidget::invertSelection (int elementMask) +{ + mCell->setSelection (elementMask, Cell::Selection_Invert); + flagAsModified(); +} + +void CSVRender::UnpagedWorldspaceWidget::selectAll (int elementMask) +{ + mCell->setSelection (elementMask, Cell::Selection_All); + flagAsModified(); +} + +void CSVRender::UnpagedWorldspaceWidget::selectAllWithSameParentId (int elementMask) +{ + mCell->selectAllWithSameParentId (elementMask); + flagAsModified(); +} + +std::string CSVRender::UnpagedWorldspaceWidget::getCellId (const osg::Vec3f& point) const +{ + return mCellId; +} + +CSVRender::Cell* CSVRender::UnpagedWorldspaceWidget::getCell(const osg::Vec3d& point) const +{ + return mCell.get(); +} + +std::vector > CSVRender::UnpagedWorldspaceWidget::getSelection ( + unsigned int elementMask) const +{ + return mCell->getSelection (elementMask); +} + +std::vector > CSVRender::UnpagedWorldspaceWidget::getEdited ( + unsigned int elementMask) const +{ + return mCell->getEdited (elementMask); +} + +void CSVRender::UnpagedWorldspaceWidget::setSubMode (int subMode, unsigned int elementMask) +{ + mCell->setSubMode (subMode, elementMask); +} + +void CSVRender::UnpagedWorldspaceWidget::reset (unsigned int elementMask) +{ + mCell->reset (elementMask); +} + void CSVRender::UnpagedWorldspaceWidget::referenceableDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { @@ -162,12 +208,81 @@ void CSVRender::UnpagedWorldspaceWidget::referenceAdded (const QModelIndex& pare flagAsModified(); } +void CSVRender::UnpagedWorldspaceWidget::pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); + + int rowStart = -1; + int rowEnd = -1; + + if (topLeft.parent().isValid()) + { + rowStart = topLeft.parent().row(); + rowEnd = bottomRight.parent().row(); + } + else + { + rowStart = topLeft.row(); + rowEnd = bottomRight.row(); + } + + for (int row = rowStart; row <= rowEnd; ++row) + { + const CSMWorld::Pathgrid& pathgrid = pathgrids.getRecord(row).get(); + if (mCellId == pathgrid.mId) + { + mCell->pathgridModified(); + flagAsModified(); + return; + } + } +} + +void CSVRender::UnpagedWorldspaceWidget::pathgridAboutToBeRemoved (const QModelIndex& parent, int start, int end) +{ + const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); + + if (!parent.isValid()) + { + // Pathgrid going to be deleted + for (int row = start; row <= end; ++row) + { + const CSMWorld::Pathgrid& pathgrid = pathgrids.getRecord(row).get(); + if (mCellId == pathgrid.mId) + { + mCell->pathgridRemoved(); + flagAsModified(); + return; + } + } + } +} + +void CSVRender::UnpagedWorldspaceWidget::pathgridAdded (const QModelIndex& parent, int start, int end) +{ + const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); + + if (!parent.isValid()) + { + for (int row = start; row <= end; ++row) + { + const CSMWorld::Pathgrid& pathgrid = pathgrids.getRecord(row).get(); + if (mCellId == pathgrid.mId) + { + mCell->pathgridModified(); + flagAsModified(); + return; + } + } + } +} + void CSVRender::UnpagedWorldspaceWidget::addVisibilitySelectorButtons ( CSVWidget::SceneToolToggle2 *tool) { WorldspaceWidget::addVisibilitySelectorButtons (tool); - tool->addButton (Element_Terrain, "Terrain", "", true); - tool->addButton (Element_Fog, "Fog"); + tool->addButton (Button_Terrain, Mask_Terrain, "Terrain", "", true); + tool->addButton (Button_Fog, Mask_Fog, "Fog"); } std::string CSVRender::UnpagedWorldspaceWidget::getStartupInstruction() diff --git a/apps/opencs/view/render/unpagedworldspacewidget.hpp b/apps/opencs/view/render/unpagedworldspacewidget.hpp index 9e6fa97f43..57e8d1a198 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.hpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.hpp @@ -25,6 +25,7 @@ namespace CSVRender { Q_OBJECT + CSMDoc::Document& mDocument; std::string mCellId; CSMWorld::IdTable *mCellsModel; CSMWorld::IdTable *mReferenceablesModel; @@ -46,6 +47,33 @@ namespace CSVRender /// \param elementMask Elements to be affected by the clear operation virtual void clearSelection (int elementMask); + /// \param elementMask Elements to be affected by the select operation + virtual void invertSelection (int elementMask); + + /// \param elementMask Elements to be affected by the select operation + virtual void selectAll (int elementMask); + + // Select everything that references the same ID as at least one of the elements + // already selected + // + /// \param elementMask Elements to be affected by the select operation + virtual void selectAllWithSameParentId (int elementMask); + + virtual std::string getCellId (const osg::Vec3f& point) const; + + virtual Cell* getCell(const osg::Vec3d& point) const; + + virtual std::vector > getSelection (unsigned int elementMask) + const; + + virtual std::vector > getEdited (unsigned int elementMask) + const; + + virtual void setSubMode (int subMode, unsigned int elementMask); + + /// Erase all overrides and restore the visual representation to its true state. + virtual void reset (unsigned int elementMask); + private: virtual void referenceableDataChanged (const QModelIndex& topLeft, @@ -61,6 +89,13 @@ namespace CSVRender virtual void referenceAdded (const QModelIndex& index, int start, int end); + virtual void pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + virtual void pathgridAboutToBeRemoved (const QModelIndex& parent, int start, int end); + + virtual void pathgridAdded (const QModelIndex& parent, int start, int end); + + virtual std::string getStartupInstruction(); protected: diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index f0d3986414..782dc53541 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -1,7 +1,6 @@ #include "worldspacewidget.hpp" #include -#include #include #include @@ -12,9 +11,6 @@ #include #include -#include -#include - #include #include "../../model/world/universalid.hpp" @@ -22,19 +18,23 @@ #include "../../model/prefs/state.hpp" +#include "../render/orbitcameramode.hpp" + #include "../widget/scenetoolmode.hpp" #include "../widget/scenetooltoggle2.hpp" #include "../widget/scenetoolrun.hpp" #include "object.hpp" -#include "elements.hpp" -#include "editmode.hpp" +#include "mask.hpp" #include "instancemode.hpp" +#include "pathgridmode.hpp" +#include "cameracontroller.hpp" CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidget* parent) -: SceneWidget (document.getData().getResourceSystem(), parent), mSceneElements(0), mRun(0), mDocument(document), - mInteractionMask (0), mEditMode (0), mLocked (false), mDragging (false), - mToolTipPos (-1, -1) +: SceneWidget (document.getData().getResourceSystem(), parent, 0, false), mSceneElements(0), mRun(0), mDocument(document), + mInteractionMask (0), mEditMode (0), mLocked (false), mDragging (false), mDragX(0), mDragY(0), mDragFactor(0), + mDragWheelFactor(0), mDragShiftFactor(0), + mToolTipPos (-1, -1), mShowToolTips(false), mToolTipDelay(0) { setAcceptDrops(true); @@ -58,6 +58,15 @@ CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidg connect (references, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (referenceAdded (const QModelIndex&, int, int))); + QAbstractItemModel *pathgrids = document.getData().getTableModel (CSMWorld::UniversalId::Type_Pathgrids); + + connect (pathgrids, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (pathgridDataChanged (const QModelIndex&, const QModelIndex&))); + connect (pathgrids, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), + this, SLOT (pathgridAboutToBeRemoved (const QModelIndex&, int, int))); + connect (pathgrids, SIGNAL (rowsInserted (const QModelIndex&, int, int)), + this, SLOT (pathgridAdded (const QModelIndex&, int, int))); + QAbstractItemModel *debugProfiles = document.getData().getTableModel (CSMWorld::UniversalId::Type_DebugProfiles); @@ -66,13 +75,11 @@ CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidg connect (debugProfiles, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (debugProfileAboutToBeRemoved (const QModelIndex&, int, int))); - connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), - this, SLOT (settingChanged (const CSMPrefs::Setting *))); - CSMPrefs::get()["3D Scene Input"].update(); - CSMPrefs::get()["Tooltips"].update(); - mToolTipDelayTimer.setSingleShot (true); connect (&mToolTipDelayTimer, SIGNAL (timeout()), this, SLOT (showToolTip())); + + CSMPrefs::get()["3D Scene Input"].update(); + CSMPrefs::get()["Tooltips"].update(); } CSVRender::WorldspaceWidget::~WorldspaceWidget () @@ -81,9 +88,6 @@ CSVRender::WorldspaceWidget::~WorldspaceWidget () void CSVRender::WorldspaceWidget::settingChanged (const CSMPrefs::Setting *setting) { - if (storeMappingSetting (setting)) - return; - if (*setting=="3D Scene Input/drag-factor") mDragFactor = setting->toDouble(); else if (*setting=="3D Scene Input/drag-wheel-factor") @@ -94,23 +98,29 @@ void CSVRender::WorldspaceWidget::settingChanged (const CSMPrefs::Setting *setti mToolTipDelay = setting->toInt(); else if (*setting=="Tooltips/scene") mShowToolTips = setting->isTrue(); + else + SceneWidget::settingChanged(setting); } -void CSVRender::WorldspaceWidget::selectNavigationMode (const std::string& mode) -{ - if (mode=="1st") - mView->setCameraManipulator(new osgGA::FirstPersonManipulator); - else if (mode=="free") - mView->setCameraManipulator(new osgGA::FirstPersonManipulator); - else if (mode=="orbit") - mView->setCameraManipulator(new osgGA::OrbitManipulator); -} void CSVRender::WorldspaceWidget::useViewHint (const std::string& hint) {} void CSVRender::WorldspaceWidget::selectDefaultNavigationMode() { - mView->setCameraManipulator(new osgGA::FirstPersonManipulator); + selectNavigationMode("1st"); +} + +void CSVRender::WorldspaceWidget::centerOrbitCameraOnSelection() +{ + std::vector > selection = getSelection(~0); + + for (std::vector >::iterator it = selection.begin(); it!=selection.end(); ++it) + { + if (CSVRender::ObjectTag *objectTag = dynamic_cast (it->get())) + { + mOrbitCamControl->setCenter(objectTag->mObject->getPosition().asVec3()); + } + } } CSVWidget::SceneToolMode *CSVRender::WorldspaceWidget::makeNavigationSelector ( @@ -124,28 +134,30 @@ CSVWidget::SceneToolMode *CSVRender::WorldspaceWidget::makeNavigationSelector ( "First Person" "
  • Mouse-Look while holding the left button
  • " "
  • WASD movement keys
  • " - "
  • Mouse wheel moves the camera forawrd/backward
  • " - "
  • Stafing (also vertically) by holding the left mouse button and control
  • " + "
  • Mouse wheel moves the camera forward/backward
  • " + "
  • Strafing (also vertically) by holding the left mouse button and control
  • " "
  • Camera is held upright
  • " "
  • Hold shift to speed up movement
  • " "
"); tool->addButton (":scenetoolbar/free-camera", "free", "Free Camera" "
  • Mouse-Look while holding the left button
  • " - "
  • Stafing (also vertically) via WASD or by holding the left mouse button and control
  • " - "
  • Mouse wheel moves the camera forawrd/backward
  • " + "
  • Strafing (also vertically) via WASD or by holding the left mouse button and control
  • " + "
  • Mouse wheel moves the camera forward/backward
  • " "
  • Roll camera with Q and E keys
  • " "
  • Hold shift to speed up movement
  • " "
"); - tool->addButton (":scenetoolbar/orbiting-camera", "orbit", - "Orbiting Camera" - "
  • Always facing the centre point
  • " - "
  • Rotate around the centre point via WASD or by moving the mouse while holding the left button
  • " - "
  • Mouse wheel moves camera away or towards centre point but can not pass through it
  • " - "
  • Roll camera with Q and E keys
  • " - "
  • Stafing (also vertically) by holding the left mouse button and control (includes relocation of the centre point)
  • " - "
  • Hold shift to speed up movement
  • " - "
"); + tool->addButton( + new CSVRender::OrbitCameraMode(this, QIcon(":scenetoolbar/orbiting-camera"), + "Orbiting Camera" + "
  • Always facing the centre point
  • " + "
  • Rotate around the centre point via WASD or by moving the mouse while holding the left button
  • " + "
  • Mouse wheel moves camera away or towards centre point but can not pass through it
  • " + "
  • Roll camera with Q and E keys
  • " + "
  • Strafing (also vertically) by holding the left mouse button and control (includes relocation of the centre point)
  • " + "
  • Hold shift to speed up movement
  • " + "
", tool), + "orbit"); connect (tool, SIGNAL (modeChanged (const std::string&)), this, SLOT (selectNavigationMode (const std::string&))); @@ -160,7 +172,7 @@ CSVWidget::SceneToolToggle2 *CSVRender::WorldspaceWidget::makeSceneVisibilitySel addVisibilitySelectorButtons (mSceneElements); - mSceneElements->setSelection (0xffffffff); + mSceneElements->setSelectionMask (0xffffffff); connect (mSceneElements, SIGNAL (selectionChanged()), this, SLOT (elementSelectionChanged())); @@ -275,12 +287,12 @@ bool CSVRender::WorldspaceWidget::handleDrop (const std::vectorgetSelection(); + return mSceneElements->getSelectionMask(); } void CSVRender::WorldspaceWidget::setInteractionMask (unsigned int mask) { - mInteractionMask = mask | Element_CellMarker | Element_CellArrow; + mInteractionMask = mask | Mask_CellMarker | Mask_CellArrow; } unsigned int CSVRender::WorldspaceWidget::getInteractionMask() const @@ -296,18 +308,16 @@ void CSVRender::WorldspaceWidget::setEditLock (bool locked) void CSVRender::WorldspaceWidget::addVisibilitySelectorButtons ( CSVWidget::SceneToolToggle2 *tool) { - tool->addButton (Element_Reference, "Instances"); - tool->addButton (Element_Water, "Water"); - tool->addButton (Element_Pathgrid, "Pathgrid"); + tool->addButton (Button_Reference, Mask_Reference, "Instances"); + tool->addButton (Button_Water, Mask_Water, "Water"); + tool->addButton (Button_Pathgrid, Mask_Pathgrid, "Pathgrid"); } void CSVRender::WorldspaceWidget::addEditModeSelectorButtons (CSVWidget::SceneToolMode *tool) { /// \todo replace EditMode with suitable subclasses tool->addButton (new InstanceMode (this, tool), "object"); - tool->addButton ( - new EditMode (this, QIcon (":placeholder"), Element_Pathgrid, "Pathgrid editing"), - "pathgrid"); + tool->addButton (new PathgridMode (this, tool), "pathgrid"); } CSMDoc::Document& CSVRender::WorldspaceWidget::getDocument() @@ -315,6 +325,94 @@ CSMDoc::Document& CSVRender::WorldspaceWidget::getDocument() return mDocument; } +CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick (const QPoint& localPos, + unsigned int interactionMask) const +{ + // (0,0) is considered the lower left corner of an OpenGL window + int x = localPos.x(); + int y = height() - localPos.y(); + + // Convert from screen space to world space + osg::Matrixd wpvMat; + wpvMat.preMult (mView->getCamera()->getViewport()->computeWindowMatrix()); + wpvMat.preMult (mView->getCamera()->getProjectionMatrix()); + wpvMat.preMult (mView->getCamera()->getViewMatrix()); + wpvMat = osg::Matrixd::inverse (wpvMat); + + osg::Vec3d start = wpvMat.preMult (osg::Vec3d(x, y, 0)); + osg::Vec3d end = wpvMat.preMult (osg::Vec3d(x, y, 1)); + osg::Vec3d direction = end - start; + + // Get intersection + osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector( + osgUtil::Intersector::MODEL, start, end)); + + intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT); + osgUtil::IntersectionVisitor visitor(intersector); + + visitor.setTraversalMask(interactionMask); + + mView->getCamera()->accept(visitor); + + // Get relevant data + for (osgUtil::LineSegmentIntersector::Intersections::iterator it = intersector->getIntersections().begin(); + it != intersector->getIntersections().end(); ++it) + { + osgUtil::LineSegmentIntersector::Intersection intersection = *it; + + // reject back-facing polygons + if (direction * intersection.getWorldIntersectNormal() > 0) + { + continue; + } + + for (std::vector::iterator it = intersection.nodePath.begin(); it != intersection.nodePath.end(); ++it) + { + osg::Node* node = *it; + if (osg::ref_ptr tag = dynamic_cast(node->getUserData())) + { + WorldspaceHitResult hit = { true, tag, 0, 0, 0, intersection.getWorldIntersectPoint() }; + if (intersection.indexList.size() >= 3) + { + hit.index0 = intersection.indexList[0]; + hit.index1 = intersection.indexList[1]; + hit.index2 = intersection.indexList[2]; + } + return hit; + } + } + + // Something untagged, probably terrain + WorldspaceHitResult hit = { true, 0, 0, 0, 0, intersection.getWorldIntersectPoint() }; + if (intersection.indexList.size() >= 3) + { + hit.index0 = intersection.indexList[0]; + hit.index1 = intersection.indexList[1]; + hit.index2 = intersection.indexList[2]; + } + return hit; + } + + // Default placement + direction.normalize(); + direction *= CSMPrefs::get()["Scene Drops"]["distance"].toInt(); + + WorldspaceHitResult hit = { false, 0, 0, 0, 0, start + direction }; + return hit; +} + +void CSVRender::WorldspaceWidget::abortDrag() +{ + if (mDragging) + { + EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); + + editMode.dragAborted(); + mDragging = false; + mDragMode.clear(); + } +} + void CSVRender::WorldspaceWidget::dragEnterEvent (QDragEnterEvent* event) { const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); @@ -357,96 +455,41 @@ void CSVRender::WorldspaceWidget::dragMoveEvent(QDragMoveEvent *event) bool CSVRender::WorldspaceWidget::storeMappingSetting (const CSMPrefs::Setting *setting) { - if (setting->getParent()->getKey()!="3D Scene Input") - return false; - static const char * const sMappingSettings[] = { - "p-navi", "s-navi", "p-edit", "s-edit", "p-select", "s-select", 0 }; - for (int i=0; sMappingSettings[i]; ++i) - if (setting->getKey()==sMappingSettings[i]) - { - QString value = QString::fromUtf8 (setting->toString().c_str()); - - Qt::MouseButton button = Qt::NoButton; - - if (value.endsWith ("Left Mouse-Button")) - button = Qt::LeftButton; - else if (value.endsWith ("Right Mouse-Button")) - button = Qt::RightButton; - else if (value.endsWith ("Middle Mouse-Button")) - button = Qt::MiddleButton; - else - return false; - - bool ctrl = value.startsWith ("Ctrl-"); - - mButtonMapping[std::make_pair (button, ctrl)] = sMappingSettings[i]; - return true; - } - - return false; -} - -osg::ref_ptr CSVRender::WorldspaceWidget::mousePick (const QPoint& localPos) -{ - // (0,0) is considered the lower left corner of an OpenGL window - int x = localPos.x(); - int y = height() - localPos.y(); - - osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector(osgUtil::Intersector::WINDOW, x, y)); - - intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT); - osgUtil::IntersectionVisitor visitor(intersector); - - visitor.setTraversalMask(getInteractionMask() << 1); - - mView->getCamera()->accept(visitor); - - for (osgUtil::LineSegmentIntersector::Intersections::iterator it = intersector->getIntersections().begin(); - it != intersector->getIntersections().end(); ++it) + if (setting->getParent()->getKey()=="3D Scene Input") { - osgUtil::LineSegmentIntersector::Intersection intersection = *it; - - // reject back-facing polygons - osg::Vec3f normal = intersection.getWorldIntersectNormal(); - normal = osg::Matrix::transform3x3(normal, mView->getCamera()->getViewMatrix()); - if (normal.z() < 0) - continue; - - for (std::vector::iterator it = intersection.nodePath.begin(); it != intersection.nodePath.end(); ++it) + for (int i=0; sMappingSettings[i]; ++i) { - osg::Node* node = *it; - if (osg::ref_ptr tag = dynamic_cast(node->getUserData())) - return tag; - } + if (setting->getKey()==sMappingSettings[i]) + { + QString value = QString::fromUtf8 (setting->toString().c_str()); -// ignoring terrain for now - // must be terrain, report coordinates -// std::cout << "Terrain hit at " << intersection.getWorldIntersectPoint().x() << " " << intersection.getWorldIntersectPoint().y() << std::endl; -// return; + Qt::MouseButton button = Qt::NoButton; + + if (value.endsWith ("Left Mouse-Button")) + button = Qt::LeftButton; + else if (value.endsWith ("Right Mouse-Button")) + button = Qt::RightButton; + else if (value.endsWith ("Middle Mouse-Button")) + button = Qt::MiddleButton; + else + return false; + + bool ctrl = value.startsWith ("Ctrl-"); + + mButtonMapping[std::make_pair (button, ctrl)] = sMappingSettings[i]; + return true; + } + } } - return osg::ref_ptr(); -} - -std::string CSVRender::WorldspaceWidget::mapButton (QMouseEvent *event) -{ - std::pair phyiscal ( - event->button(), event->modifiers() & Qt::ControlModifier); - - std::map, std::string>::const_iterator iter = - mButtonMapping.find (phyiscal); - - if (iter!=mButtonMapping.end()) - return iter->second; - - return ""; + return SceneWidget::storeMappingSetting(setting); } void CSVRender::WorldspaceWidget::dropEvent (QDropEvent* event) @@ -524,6 +567,7 @@ void CSVRender::WorldspaceWidget::editModeChanged (const std::string& id) { dynamic_cast (*mEditMode->getCurrent()).setEditLock (mLocked); mDragging = false; + mDragMode.clear(); } void CSVRender::WorldspaceWidget::showToolTip() @@ -532,10 +576,11 @@ void CSVRender::WorldspaceWidget::showToolTip() { QPoint pos = QCursor::pos(); - if (osg::ref_ptr tag = mousePick (mapFromGlobal (pos))) + WorldspaceHitResult hit = mousePick (mapFromGlobal (pos), getInteractionMask()); + if (hit.tag) { bool hideBasics = CSMPrefs::get()["Tooltips"]["scene-hide-basic"].isTrue(); - QToolTip::showText (pos, tag->getToolTip (hideBasics), this); + QToolTip::showText (pos, hit.tag->getToolTip (hideBasics), this); } } } @@ -553,50 +598,7 @@ void CSVRender::WorldspaceWidget::updateOverlay() void CSVRender::WorldspaceWidget::mouseMoveEvent (QMouseEvent *event) { - if (!mDragging) - { - if (mDragMode.empty()) - { - if (event->globalPos()!=mToolTipPos) - { - mToolTipPos = event->globalPos(); - - if (mShowToolTips) - mToolTipDelayTimer.start (mToolTipDelay); - } - } - else if (mDragMode=="p-navi" || mDragMode=="s-navi") - { - - } - else if (mDragMode=="p-edit" || mDragMode=="s-edit" || mDragMode=="p-select" || mDragMode=="s-select") - { - osg::ref_ptr tag = mousePick (event->pos()); - - EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); - - if (mDragMode=="p-edit") - mDragging = editMode.primaryEditStartDrag (tag); - else if (mDragMode=="s-edit") - mDragging = editMode.secondaryEditStartDrag (tag); - else if (mDragMode=="p-select") - mDragging = editMode.primarySelectStartDrag (tag); - else if (mDragMode=="s-select") - mDragging = editMode.secondarySelectStartDrag (tag); - - if (mDragging) - { -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - mDragX = event->localPos().x(); - mDragY = height() - event->localPos().y(); -#else - mDragX = event->posF().x(); - mDragY = height() - event->posF().y(); -#endif - } - } - } - else + if (mDragging) { int diffX = event->x() - mDragX; int diffY = (height() - event->y()) - mDragY; @@ -611,7 +613,46 @@ void CSVRender::WorldspaceWidget::mouseMoveEvent (QMouseEvent *event) EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); - editMode.drag (diffX, diffY, factor); + editMode.drag (event->pos(), diffX, diffY, factor); + } + else if (mDragMode=="p-edit" || mDragMode=="s-edit" || mDragMode=="p-select" || mDragMode=="s-select") + { + EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); + + if (mDragMode=="p-edit") + mDragging = editMode.primaryEditStartDrag (event->pos()); + else if (mDragMode=="s-edit") + mDragging = editMode.secondaryEditStartDrag (event->pos()); + else if (mDragMode=="p-select") + mDragging = editMode.primarySelectStartDrag (event->pos()); + else if (mDragMode=="s-select") + mDragging = editMode.secondarySelectStartDrag (event->pos()); + + if (mDragging) + { +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + mDragX = event->localPos().x(); + mDragY = height() - event->localPos().y(); +#else + mDragX = event->posF().x(); + mDragY = height() - event->posF().y(); +#endif + } + } + else + { + if (event->globalPos()!=mToolTipPos) + { + mToolTipPos = event->globalPos(); + + if (mShowToolTips) + { + QToolTip::hideText(); + mToolTipDelayTimer.start (mToolTipDelay); + } + } + + SceneWidget::mouseMoveEvent(event); } } @@ -619,53 +660,40 @@ void CSVRender::WorldspaceWidget::mousePressEvent (QMouseEvent *event) { std::string button = mapButton (event); - if (!mDragging) - mDragMode = button; + if (button=="p-edit" || button=="s-edit" || + button=="p-select" || button=="s-select") + { + if (!mDragging) + mDragMode = button; + } + else + SceneWidget::mousePressEvent(event); } void CSVRender::WorldspaceWidget::mouseReleaseEvent (QMouseEvent *event) { std::string button = mapButton (event); + mDragMode.clear(); - if (mDragging) + if (button=="p-edit" || button=="s-edit" || + button=="p-select" || button=="s-select") { - if (mDragMode=="p-navi" || mDragMode=="s-navi") - { - - } - else if (mDragMode=="p-edit" || mDragMode=="s-edit" || - mDragMode=="p-select" || mDragMode=="s-select") + if (mDragging) { EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); - editMode.dragCompleted(); + editMode.dragCompleted(event->pos()); mDragging = false; } + else + { + WorldspaceHitResult hit = mousePick(event->pos(), getInteractionMask()); + + handleMouseClick (hit, button, event->modifiers() & Qt::ShiftModifier); + } } else - { - if (button=="p-navi" || button=="s-navi") - { - - } - else if (button=="p-edit" || button=="s-edit" || - button=="p-select" || button=="s-select") - { - osg::ref_ptr tag = mousePick (event->pos()); - - handleMouseClick (tag, button, event->modifiers() & Qt::ShiftModifier); - } - } - - mDragMode.clear(); -} - -void CSVRender::WorldspaceWidget::mouseDoubleClickEvent (QMouseEvent *event) -{ - if(event->button() == Qt::RightButton) - { - //mMouse->mouseDoubleClickEvent(event); - } + SceneWidget::mouseReleaseEvent(event); } void CSVRender::WorldspaceWidget::wheelEvent (QWheelEvent *event) @@ -681,34 +709,35 @@ void CSVRender::WorldspaceWidget::wheelEvent (QWheelEvent *event) editMode.dragWheel (event->delta(), factor); } + else + SceneWidget::wheelEvent(event); } void CSVRender::WorldspaceWidget::keyPressEvent (QKeyEvent *event) { if(event->key() == Qt::Key_Escape) { - if (mDragging) - { - EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); - - editMode.dragAborted(); - mDragging = false; - } + abortDrag(); } else - RenderWidget::keyPressEvent(event); + SceneWidget::keyPressEvent(event); } -void CSVRender::WorldspaceWidget::handleMouseClick (osg::ref_ptr tag, const std::string& button, bool shift) +void CSVRender::WorldspaceWidget::handleMouseClick (const WorldspaceHitResult& hit, const std::string& button, bool shift) { EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); if (button=="p-edit") - editMode.primaryEditPressed (tag); + editMode.primaryEditPressed (hit); else if (button=="s-edit") - editMode.secondaryEditPressed (tag); + editMode.secondaryEditPressed (hit); else if (button=="p-select") - editMode.primarySelectPressed (tag); + editMode.primarySelectPressed (hit); else if (button=="s-select") - editMode.secondarySelectPressed (tag); + editMode.secondarySelectPressed (hit); +} + +CSVRender::EditMode *CSVRender::WorldspaceWidget::getEditMode() +{ + return dynamic_cast (mEditMode->getCurrent()); } diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 54376cee45..92d31eb9ee 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -1,17 +1,16 @@ #ifndef OPENCS_VIEW_WORLDSPACEWIDGET_H #define OPENCS_VIEW_WORLDSPACEWIDGET_H -#include - #include #include +#include #include "../../model/doc/document.hpp" #include "../../model/world/tablemimedata.hpp" #include "scenewidget.hpp" -#include "elements.hpp" +#include "mask.hpp" namespace CSMPrefs { @@ -34,7 +33,17 @@ namespace CSVWidget namespace CSVRender { class TagBase; + class Cell; class CellArrow; + class EditMode; + + struct WorldspaceHitResult + { + bool hit; + osg::ref_ptr tag; + unsigned int index0, index1, index2; // indices of mesh vertices + osg::Vec3d worldPos; + }; class WorldspaceWidget : public SceneWidget { @@ -44,7 +53,6 @@ namespace CSVRender CSVWidget::SceneToolRun *mRun; CSMDoc::Document& mDocument; unsigned int mInteractionMask; - std::map, std::string> mButtonMapping; CSVWidget::SceneToolMode *mEditMode; bool mLocked; std::string mDragMode; @@ -99,6 +107,8 @@ namespace CSVRender void selectDefaultNavigationMode(); + void centerOrbitCameraOnSelection(); + static DropType getDropType(const std::vector& data); virtual dropRequirments getDropRequirements(DropType type) const; @@ -127,8 +137,59 @@ namespace CSVRender /// \param elementMask Elements to be affected by the clear operation virtual void clearSelection (int elementMask) = 0; + /// \param elementMask Elements to be affected by the select operation + virtual void invertSelection (int elementMask) = 0; + + /// \param elementMask Elements to be affected by the select operation + virtual void selectAll (int elementMask) = 0; + + // Select everything that references the same ID as at least one of the elements + // already selected + // + /// \param elementMask Elements to be affected by the select operation + virtual void selectAllWithSameParentId (int elementMask) = 0; + + /// Return the next intersection with scene elements matched by + /// \a interactionMask based on \a localPos and the camera vector. + /// If there is no such intersection, instead a point "in front" of \a localPos will be + /// returned. + WorldspaceHitResult mousePick (const QPoint& localPos, unsigned int interactionMask) const; + + virtual std::string getCellId (const osg::Vec3f& point) const = 0; + + /// \note Returns the cell if it exists, otherwise a null pointer + virtual Cell* getCell(const osg::Vec3d& point) const = 0; + + virtual std::vector > getSelection (unsigned int elementMask) + const = 0; + + virtual std::vector > getEdited (unsigned int elementMask) + const = 0; + + virtual void setSubMode (int subMode, unsigned int elementMask) = 0; + + /// Erase all overrides and restore the visual representation to its true state. + virtual void reset (unsigned int elementMask) = 0; + + /// \note Drags will be automatically aborted when the aborting is triggered + /// (either explicitly or implicitly) from within this class. This function only + /// needs to be called, when the drag abort is triggered externally (e.g. from + /// an edit mode). + void abortDrag(); + protected: + /// Visual elements in a scene + /// @note do not change the enumeration values, they are used in pre-existing button file names! + enum ButtonId + { + Button_Reference = 0x1, + Button_Pathgrid = 0x2, + Button_Water = 0x4, + Button_Fog = 0x8, + Button_Terrain = 0x10 + }; + virtual void addVisibilitySelectorButtons (CSVWidget::SceneToolToggle2 *tool); virtual void addEditModeSelectorButtons (CSVWidget::SceneToolMode *tool); @@ -138,13 +199,19 @@ namespace CSVRender virtual void mouseMoveEvent (QMouseEvent *event); virtual void mousePressEvent (QMouseEvent *event); virtual void mouseReleaseEvent (QMouseEvent *event); - virtual void mouseDoubleClickEvent (QMouseEvent *event); virtual void wheelEvent (QWheelEvent *event); virtual void keyPressEvent (QKeyEvent *event); - virtual void handleMouseClick (osg::ref_ptr tag, const std::string& button, + virtual void handleMouseClick (const WorldspaceHitResult& hit, const std::string& button, bool shift); + /// \return Is \a key a button mapping setting? (ignored otherwise) + virtual bool storeMappingSetting (const CSMPrefs::Setting *setting); + + virtual void settingChanged (const CSMPrefs::Setting *setting); + + EditMode *getEditMode(); + private: void dragEnterEvent(QDragEnterEvent *event); @@ -153,21 +220,10 @@ namespace CSVRender void dragMoveEvent(QDragMoveEvent *event); - /// \return Is \a key a button mapping setting? (ignored otherwise) - bool storeMappingSetting (const CSMPrefs::Setting *setting); - - osg::ref_ptr mousePick (const QPoint& localPos); - - std::string mapButton (QMouseEvent *event); - virtual std::string getStartupInstruction() = 0; private slots: - void settingChanged (const CSMPrefs::Setting *setting); - - void selectNavigationMode (const std::string& mode); - virtual void referenceableDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) = 0; @@ -181,6 +237,13 @@ namespace CSVRender virtual void referenceAdded (const QModelIndex& index, int start, int end) = 0; + virtual void pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) = 0; + + virtual void pathgridAboutToBeRemoved (const QModelIndex& parent, int start, int end) = 0; + + virtual void pathgridAdded (const QModelIndex& parent, int start, int end) = 0; + + virtual void runRequest (const std::string& profile); void debugProfileDataChanged (const QModelIndex& topLeft, diff --git a/apps/opencs/view/widget/modebutton.cpp b/apps/opencs/view/widget/modebutton.cpp index 7c62f6bb16..88f0502479 100644 --- a/apps/opencs/view/widget/modebutton.cpp +++ b/apps/opencs/view/widget/modebutton.cpp @@ -7,3 +7,8 @@ CSVWidget::ModeButton::ModeButton (const QIcon& icon, const QString& tooltip, QW void CSVWidget::ModeButton::activate (SceneToolbar *toolbar) {} void CSVWidget::ModeButton::deactivate (SceneToolbar *toolbar) {} + +bool CSVWidget::ModeButton::createContextMenu (QMenu *menu) +{ + return false; +} diff --git a/apps/opencs/view/widget/modebutton.hpp b/apps/opencs/view/widget/modebutton.hpp index ac14afc952..1615ff298a 100644 --- a/apps/opencs/view/widget/modebutton.hpp +++ b/apps/opencs/view/widget/modebutton.hpp @@ -3,6 +3,8 @@ #include "pushbutton.hpp" +class QMenu; + namespace CSVWidget { class SceneToolbar; @@ -22,6 +24,14 @@ namespace CSVWidget /// Default-Implementation: do nothing virtual void deactivate (SceneToolbar *toolbar); + + /// Add context menu items to \a menu. Default-implementation: return false + /// + /// \attention menu can be a 0-pointer + /// + /// \return Have there been any menu items to be added (if menu is 0 and there + /// items to be added, the function must return true anyway. + virtual bool createContextMenu (QMenu *menu); }; } diff --git a/apps/opencs/view/widget/scenetoolmode.cpp b/apps/opencs/view/widget/scenetoolmode.cpp index a93bb05567..c91890c697 100644 --- a/apps/opencs/view/widget/scenetoolmode.cpp +++ b/apps/opencs/view/widget/scenetoolmode.cpp @@ -3,10 +3,27 @@ #include #include #include +#include +#include #include "scenetoolbar.hpp" #include "modebutton.hpp" +void CSVWidget::SceneToolMode::contextMenuEvent (QContextMenuEvent *event) +{ + QMenu menu (this); + if (createContextMenu (&menu)) + menu.exec (event->globalPos()); +} + +bool CSVWidget::SceneToolMode::createContextMenu (QMenu *menu) +{ + if (mCurrent) + return mCurrent->createContextMenu (menu); + + return false; +} + void CSVWidget::SceneToolMode::adjustToolTip (const ModeButton *activeMode) { QString toolTip = mToolTip; @@ -15,9 +32,33 @@ void CSVWidget::SceneToolMode::adjustToolTip (const ModeButton *activeMode) toolTip += "

(left click to change mode)"; + if (createContextMenu (0)) + toolTip += "
(right click to access context menu)"; + setToolTip (toolTip); } +void CSVWidget::SceneToolMode::setButton (std::map::iterator iter) +{ + for (std::map::const_iterator iter2 = mButtons.begin(); + iter2!=mButtons.end(); ++iter2) + iter2->first->setChecked (iter2==iter); + + setIcon (iter->first->icon()); + adjustToolTip (iter->first); + + if (mCurrent!=iter->first) + { + if (mCurrent) + mCurrent->deactivate (mToolbar); + + mCurrent = iter->first; + mCurrent->activate (mToolbar); + } + + emit modeChanged (iter->second); +} + CSVWidget::SceneToolMode::SceneToolMode (SceneToolbar *parent, const QString& toolTip) : SceneTool (parent), mButtonSize (parent->getButtonSize()), mIconSize (parent->getIconSize()), mToolTip (toolTip), mFirst (0), mCurrent (0), mToolbar (parent) @@ -76,9 +117,25 @@ CSVWidget::ModeButton *CSVWidget::SceneToolMode::getCurrent() return mCurrent; } +std::string CSVWidget::SceneToolMode::getCurrentId() const +{ + return mButtons.find (mCurrent)->second; +} + +void CSVWidget::SceneToolMode::setButton (const std::string& id) +{ + for (std::map::iterator iter = mButtons.begin(); + iter!=mButtons.end(); ++iter) + if (iter->second==id) + { + setButton (iter); + break; + } +} + void CSVWidget::SceneToolMode::selected() { - std::map::const_iterator iter = + std::map::iterator iter = mButtons.find (dynamic_cast (sender())); if (iter!=mButtons.end()) @@ -86,22 +143,6 @@ void CSVWidget::SceneToolMode::selected() if (!iter->first->hasKeepOpen()) mPanel->hide(); - for (std::map::const_iterator iter2 = mButtons.begin(); - iter2!=mButtons.end(); ++iter2) - iter2->first->setChecked (iter2==iter); - - setIcon (iter->first->icon()); - adjustToolTip (iter->first); - - if (mCurrent!=iter->first) - { - if (mCurrent) - mCurrent->deactivate (mToolbar); - - mCurrent = iter->first; - mCurrent->activate (mToolbar); - } - - emit modeChanged (iter->second); + setButton (iter); } } diff --git a/apps/opencs/view/widget/scenetoolmode.hpp b/apps/opencs/view/widget/scenetoolmode.hpp index 6828a22691..192b3ee78b 100644 --- a/apps/opencs/view/widget/scenetoolmode.hpp +++ b/apps/opencs/view/widget/scenetoolmode.hpp @@ -6,6 +6,7 @@ #include class QHBoxLayout; +class QMenu; namespace CSVWidget { @@ -29,6 +30,19 @@ namespace CSVWidget void adjustToolTip (const ModeButton *activeMode); + virtual void contextMenuEvent (QContextMenuEvent *event); + + /// Add context menu items to \a menu. Default-implementation: Pass on request to + /// current mode button or return false, if there is no current mode button. + /// + /// \attention menu can be a 0-pointer + /// + /// \return Have there been any menu items to be added (if menu is 0 and there + /// items to be added, the function must return true anyway. + virtual bool createContextMenu (QMenu *menu); + + void setButton (std::map::iterator iter); + public: SceneToolMode (SceneToolbar *parent, const QString& toolTip); @@ -44,6 +58,12 @@ namespace CSVWidget /// Will return a 0-pointer only if the mode does not have any buttons yet. ModeButton *getCurrent(); + /// Must not be called if there aren't any buttons yet. + std::string getCurrentId() const; + + /// Manually change the current mode + void setButton (const std::string& id); + signals: void modeChanged (const std::string& id); diff --git a/apps/opencs/view/widget/scenetooltoggle.cpp b/apps/opencs/view/widget/scenetooltoggle.cpp index d7251882a7..5919a280af 100644 --- a/apps/opencs/view/widget/scenetooltoggle.cpp +++ b/apps/opencs/view/widget/scenetooltoggle.cpp @@ -40,7 +40,7 @@ void CSVWidget::SceneToolToggle::adjustToolTip() void CSVWidget::SceneToolToggle::adjustIcon() { - unsigned int selection = getSelection(); + unsigned int selection = getSelectionMask(); if (!selection) setIcon (QIcon (QString::fromUtf8 (mEmptyIcon.c_str()))); else @@ -135,7 +135,7 @@ void CSVWidget::SceneToolToggle::showPanel (const QPoint& position) mFirst->setFocus (Qt::OtherFocusReason); } -void CSVWidget::SceneToolToggle::addButton (const std::string& icon, unsigned int id, +void CSVWidget::SceneToolToggle::addButton (const std::string& icon, unsigned int mask, const std::string& smallIcon, const QString& name, const QString& tooltip) { if (mButtons.size()>=9) @@ -151,7 +151,7 @@ void CSVWidget::SceneToolToggle::addButton (const std::string& icon, unsigned in mLayout->addWidget (button); ButtonDesc desc; - desc.mId = id; + desc.mMask = mask; desc.mSmallIcon = smallIcon; desc.mName = name; desc.mIndex = mButtons.size(); @@ -164,23 +164,23 @@ void CSVWidget::SceneToolToggle::addButton (const std::string& icon, unsigned in mFirst = button; } -unsigned int CSVWidget::SceneToolToggle::getSelection() const +unsigned int CSVWidget::SceneToolToggle::getSelectionMask() const { unsigned int selection = 0; for (std::map::const_iterator iter (mButtons.begin()); iter!=mButtons.end(); ++iter) if (iter->first->isChecked()) - selection |= iter->second.mId; + selection |= iter->second.mMask; return selection; } -void CSVWidget::SceneToolToggle::setSelection (unsigned int selection) +void CSVWidget::SceneToolToggle::setSelectionMask (unsigned int selection) { for (std::map::iterator iter (mButtons.begin()); iter!=mButtons.end(); ++iter) - iter->first->setChecked (selection & iter->second.mId); + iter->first->setChecked (selection & iter->second.mMask); adjustToolTip(); adjustIcon(); diff --git a/apps/opencs/view/widget/scenetooltoggle.hpp b/apps/opencs/view/widget/scenetooltoggle.hpp index 55e6975249..68cd2362e6 100644 --- a/apps/opencs/view/widget/scenetooltoggle.hpp +++ b/apps/opencs/view/widget/scenetooltoggle.hpp @@ -20,7 +20,7 @@ namespace CSVWidget struct ButtonDesc { - unsigned int mId; + unsigned int mMask; std::string mSmallIcon; QString mName; int mIndex; @@ -54,13 +54,13 @@ namespace CSVWidget /// \note The layout algorithm can not handle more than 9 buttons. To prevent this An /// attempt to add more will result in an exception being thrown. /// The small icons will be sized at (x-4)/3 (where x is the main icon size). - void addButton (const std::string& icon, unsigned int id, + void addButton (const std::string& icon, unsigned int mask, const std::string& smallIcon, const QString& name, const QString& tooltip = ""); - unsigned int getSelection() const; + unsigned int getSelectionMask() const; - /// \param or'ed button IDs. IDs that do not exist will be ignored. - void setSelection (unsigned int selection); + /// \param or'ed button masks. buttons that do not exist will be ignored. + void setSelectionMask (unsigned int selection); signals: diff --git a/apps/opencs/view/widget/scenetooltoggle2.cpp b/apps/opencs/view/widget/scenetooltoggle2.cpp index e0431476eb..720da6a964 100644 --- a/apps/opencs/view/widget/scenetooltoggle2.cpp +++ b/apps/opencs/view/widget/scenetooltoggle2.cpp @@ -41,8 +41,15 @@ void CSVWidget::SceneToolToggle2::adjustToolTip() void CSVWidget::SceneToolToggle2::adjustIcon() { + unsigned int buttonIds = 0; + + for (std::map::const_iterator iter (mButtons.begin()); + iter!=mButtons.end(); ++iter) + if (iter->first->isChecked()) + buttonIds |= iter->second.mButtonId; + std::ostringstream stream; - stream << mCompositeIcon << getSelection(); + stream << mCompositeIcon << buttonIds; setIcon (QIcon (QString::fromUtf8 (stream.str().c_str()))); } @@ -70,7 +77,7 @@ void CSVWidget::SceneToolToggle2::showPanel (const QPoint& position) mFirst->setFocus (Qt::OtherFocusReason); } -void CSVWidget::SceneToolToggle2::addButton (unsigned int id, +void CSVWidget::SceneToolToggle2::addButton (unsigned int id, unsigned int mask, const QString& name, const QString& tooltip, bool disabled) { std::ostringstream stream; @@ -89,7 +96,8 @@ void CSVWidget::SceneToolToggle2::addButton (unsigned int id, mLayout->addWidget (button); ButtonDesc desc; - desc.mId = id; + desc.mButtonId = id; + desc.mMask = mask; desc.mName = name; desc.mIndex = mButtons.size(); @@ -101,23 +109,23 @@ void CSVWidget::SceneToolToggle2::addButton (unsigned int id, mFirst = button; } -unsigned int CSVWidget::SceneToolToggle2::getSelection() const +unsigned int CSVWidget::SceneToolToggle2::getSelectionMask() const { unsigned int selection = 0; for (std::map::const_iterator iter (mButtons.begin()); iter!=mButtons.end(); ++iter) if (iter->first->isChecked()) - selection |= iter->second.mId; + selection |= iter->second.mMask; return selection; } -void CSVWidget::SceneToolToggle2::setSelection (unsigned int selection) +void CSVWidget::SceneToolToggle2::setSelectionMask (unsigned int selection) { for (std::map::iterator iter (mButtons.begin()); iter!=mButtons.end(); ++iter) - iter->first->setChecked (selection & iter->second.mId); + iter->first->setChecked (selection & iter->second.mMask); adjustToolTip(); adjustIcon(); diff --git a/apps/opencs/view/widget/scenetooltoggle2.hpp b/apps/opencs/view/widget/scenetooltoggle2.hpp index 0bae780f97..50337ac117 100644 --- a/apps/opencs/view/widget/scenetooltoggle2.hpp +++ b/apps/opencs/view/widget/scenetooltoggle2.hpp @@ -22,7 +22,8 @@ namespace CSVWidget struct ButtonDesc { - unsigned int mId; + unsigned int mButtonId; + unsigned int mMask; QString mName; int mIndex; }; @@ -53,15 +54,17 @@ namespace CSVWidget virtual void showPanel (const QPoint& position); + /// \param buttonId used to compose the icon filename + /// \param mask used for the reported getSelectionMask() / setSelectionMask() /// \attention After the last button has been added, setSelection must be called at /// least once to finalise the layout. - void addButton (unsigned int id, + void addButton (unsigned int buttonId, unsigned int mask, const QString& name, const QString& tooltip = "", bool disabled = false); - unsigned int getSelection() const; + unsigned int getSelectionMask() const; - /// \param or'ed button IDs. IDs that do not exist will be ignored. - void setSelection (unsigned int selection); + /// \param or'ed button masks. buttons that do not exist will be ignored. + void setSelectionMask (unsigned int selection); signals: diff --git a/apps/opencs/view/world/datadisplaydelegate.cpp b/apps/opencs/view/world/datadisplaydelegate.cpp index 51d7137ec5..9db16d593e 100644 --- a/apps/opencs/view/world/datadisplaydelegate.cpp +++ b/apps/opencs/view/world/datadisplaydelegate.cpp @@ -140,7 +140,7 @@ void CSVWorld::DataDisplayDelegate::settingChanged (const CSMPrefs::Setting *set } -void CSVWorld::DataDisplayDelegateFactory::add (int enumValue, QString enumName, QString iconFilename) +void CSVWorld::DataDisplayDelegateFactory::add (int enumValue, const QString& enumName, const QString& iconFilename) { mIcons.push_back (std::make_pair(enumValue, QIcon(iconFilename))); EnumDelegateFactory::add(enumValue, enumName); diff --git a/apps/opencs/view/world/datadisplaydelegate.hpp b/apps/opencs/view/world/datadisplaydelegate.hpp index cde109fd4c..540216d78a 100755 --- a/apps/opencs/view/world/datadisplaydelegate.hpp +++ b/apps/opencs/view/world/datadisplaydelegate.hpp @@ -83,7 +83,7 @@ namespace CSVWorld protected: - void add (int enumValue,const QString enumName, const QString iconFilename); + void add (int enumValue, const QString& enumName, const QString& iconFilename); }; diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index 25bd8e8ee4..0a79aac2b1 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -674,8 +674,6 @@ void CSVWorld::EditWidget::remake(int row) { mNestedTableMapper->addMapping (editor, col); - std::string disString = tree->nestedHeaderData (i, col, - Qt::Horizontal, Qt::DisplayRole).toString().toStdString(); // Need to use Qt::DisplayRole in order to get the correct string // from CSMWorld::Columns QLabel* label = new QLabel (tree->nestedHeaderData (i, col, diff --git a/apps/opencs/view/world/genericcreator.cpp b/apps/opencs/view/world/genericcreator.cpp index df77399417..dd2bd82dfe 100644 --- a/apps/opencs/view/world/genericcreator.cpp +++ b/apps/opencs/view/world/genericcreator.cpp @@ -40,6 +40,10 @@ void CSVWorld::GenericCreator::insertAtBeginning (QWidget *widget, bool stretche void CSVWorld::GenericCreator::insertBeforeButtons (QWidget *widget, bool stretched) { mLayout->insertWidget (mLayout->count()-2, widget, stretched ? 1 : 0); + + // Reset tab order relative to buttons. + setTabOrder(widget, mCreate); + setTabOrder(mCreate, mCancel); } std::string CSVWorld::GenericCreator::getId() const @@ -161,15 +165,16 @@ CSVWorld::GenericCreator::GenericCreator (CSMWorld::Data& data, QUndoStack& undo mCreate = new QPushButton ("Create"); mLayout->addWidget (mCreate); - QPushButton *cancelButton = new QPushButton ("Cancel"); - mLayout->addWidget (cancelButton); + mCancel = new QPushButton("Cancel"); + mLayout->addWidget(mCancel); setLayout (mLayout); - connect (cancelButton, SIGNAL (clicked (bool)), this, SIGNAL (done())); + connect (mCancel, SIGNAL (clicked (bool)), this, SIGNAL (done())); connect (mCreate, SIGNAL (clicked (bool)), this, SLOT (create())); connect (mId, SIGNAL (textChanged (const QString&)), this, SLOT (textChanged (const QString&))); + connect (mId, SIGNAL (returnPressed()), this, SLOT (inputReturnPressed())); connect (&mData, SIGNAL (idListChanged()), this, SLOT (dataIdListChanged())); } @@ -205,6 +210,14 @@ void CSVWorld::GenericCreator::textChanged (const QString& text) update(); } +void CSVWorld::GenericCreator::inputReturnPressed() +{ + if (mCreate->isEnabled()) + { + create(); + } +} + void CSVWorld::GenericCreator::create() { if (!mLocked) diff --git a/apps/opencs/view/world/genericcreator.hpp b/apps/opencs/view/world/genericcreator.hpp index f63c451092..f3fc82ec17 100644 --- a/apps/opencs/view/world/genericcreator.hpp +++ b/apps/opencs/view/world/genericcreator.hpp @@ -33,6 +33,7 @@ namespace CSVWorld QUndoStack& mUndoStack; CSMWorld::UniversalId mListId; QPushButton *mCreate; + QPushButton *mCancel; QLineEdit *mId; std::string mErrors; QHBoxLayout *mLayout; @@ -56,6 +57,9 @@ namespace CSVWorld void insertAtBeginning (QWidget *widget, bool stretched); + /// \brief Insert given widget before Create and Cancel buttons. + /// \param widget Widget to add to layout. + /// \param stretched Whether widget should be streched or not. void insertBeforeButtons (QWidget *widget, bool stretched); virtual std::string getId() const; @@ -112,6 +116,9 @@ namespace CSVWorld void textChanged (const QString& text); + /// \brief Create record if able to after Return key is pressed on input. + void inputReturnPressed(); + void create(); void scopeChanged (int index); diff --git a/apps/opencs/view/world/idcompletiondelegate.cpp b/apps/opencs/view/world/idcompletiondelegate.cpp index 970490828f..7f0f4ae460 100644 --- a/apps/opencs/view/world/idcompletiondelegate.cpp +++ b/apps/opencs/view/world/idcompletiondelegate.cpp @@ -1,6 +1,7 @@ #include "idcompletiondelegate.hpp" #include "../../model/world/idcompletionmanager.hpp" +#include "../../model/world/infoselectwrapper.hpp" #include "../widget/droplineedit.hpp" @@ -27,6 +28,56 @@ QWidget *CSVWorld::IdCompletionDelegate::createEditor(QWidget *parent, return NULL; } + // The completer for InfoCondVar needs to return a completer based on the first column + if (display == CSMWorld::ColumnBase::Display_InfoCondVar) + { + QModelIndex sibling = index.sibling(index.row(), 0); + int conditionFunction = sibling.model()->data(sibling, Qt::EditRole).toInt(); + + switch (conditionFunction) + { + case CSMWorld::ConstInfoSelectWrapper::Function_Global: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_GlobalVariable); + } + case CSMWorld::ConstInfoSelectWrapper::Function_Journal: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Journal); + } + case CSMWorld::ConstInfoSelectWrapper::Function_Item: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Referenceable); + } + case CSMWorld::ConstInfoSelectWrapper::Function_Dead: + case CSMWorld::ConstInfoSelectWrapper::Function_NotId: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Referenceable); + } + case CSMWorld::ConstInfoSelectWrapper::Function_NotFaction: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Faction); + } + case CSMWorld::ConstInfoSelectWrapper::Function_NotClass: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Class); + } + case CSMWorld::ConstInfoSelectWrapper::Function_NotRace: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Race); + } + case CSMWorld::ConstInfoSelectWrapper::Function_NotCell: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Cell); + } + case CSMWorld::ConstInfoSelectWrapper::Function_Local: + case CSMWorld::ConstInfoSelectWrapper::Function_NotLocal: + { + return new CSVWidget::DropLineEdit(display, parent); + } + default: return 0; // The rest of them can't be edited anyway + } + } + CSMWorld::IdCompletionManager &completionManager = getDocument().getIdCompletionManager(); CSVWidget::DropLineEdit *editor = new CSVWidget::DropLineEdit(display, parent); editor->setCompleter(completionManager.getCompleter(display).get()); diff --git a/apps/opencs/view/world/infocreator.cpp b/apps/opencs/view/world/infocreator.cpp index 1139afd691..243b7d8799 100644 --- a/apps/opencs/view/world/infocreator.cpp +++ b/apps/opencs/view/world/infocreator.cpp @@ -60,6 +60,7 @@ CSVWorld::InfoCreator::InfoCreator (CSMWorld::Data& data, QUndoStack& undoStack, setManualEditing (false); connect (mTopic, SIGNAL (textChanged (const QString&)), this, SLOT (topicChanged())); + connect (mTopic, SIGNAL (returnPressed()), this, SLOT (inputReturnPressed())); } void CSVWorld::InfoCreator::cloneMode (const std::string& originId, @@ -110,7 +111,7 @@ void CSVWorld::InfoCreator::topicChanged() update(); } -CSVWorld::Creator *CSVWorld::InfoCreatorFactory::makeCreator(CSMDoc::Document& document, +CSVWorld::Creator *CSVWorld::InfoCreatorFactory::makeCreator(CSMDoc::Document& document, const CSMWorld::UniversalId& id) const { return new InfoCreator(document.getData(), diff --git a/apps/opencs/view/world/nestedtable.cpp b/apps/opencs/view/world/nestedtable.cpp index 23d5664396..d791377b81 100644 --- a/apps/opencs/view/world/nestedtable.cpp +++ b/apps/opencs/view/world/nestedtable.cpp @@ -9,6 +9,7 @@ #include "../../model/world/universalid.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/commanddispatcher.hpp" +#include "../../model/world/commandmacro.hpp" #include "tableeditidaction.hpp" #include "util.hpp" @@ -63,7 +64,7 @@ CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, connect(mAddNewRowAction, SIGNAL(triggered()), this, SLOT(addNewRowActionTriggered())); - mRemoveRowAction = new QAction (tr ("Remove row"), this); + mRemoveRowAction = new QAction (tr ("Remove rows"), this); connect(mRemoveRowAction, SIGNAL(triggered()), this, SLOT(removeRowActionTriggered())); @@ -100,10 +101,8 @@ void CSVWorld::NestedTable::contextMenuEvent (QContextMenuEvent *event) if (mAddNewRowAction && mRemoveRowAction) { - if (selectionModel()->selectedRows().size() == 1) - menu.addAction(mRemoveRowAction); - menu.addAction(mAddNewRowAction); + menu.addAction(mRemoveRowAction); } menu.exec (event->globalPos()); @@ -111,17 +110,27 @@ void CSVWorld::NestedTable::contextMenuEvent (QContextMenuEvent *event) void CSVWorld::NestedTable::removeRowActionTriggered() { - mDocument.getUndoStack().push(new CSMWorld::DeleteNestedCommand(*(mModel->model()), - mModel->getParentId(), - selectionModel()->selectedRows().begin()->row(), - mModel->getParentColumn())); + CSMWorld::CommandMacro macro(mDocument.getUndoStack(), + selectionModel()->selectedRows().size() > 1 ? tr("Remove rows") : ""); + + // Remove rows in reverse order + for (int i = selectionModel()->selectedRows().size() - 1; i >= 0; --i) + { + macro.push(new CSMWorld::DeleteNestedCommand(*(mModel->model()), mModel->getParentId(), + selectionModel()->selectedRows()[i].row(), mModel->getParentColumn())); + } } void CSVWorld::NestedTable::addNewRowActionTriggered() { + int row = 0; + + if (!selectionModel()->selectedRows().empty()) + row = selectionModel()->selectedRows().back().row() + 1; + mDocument.getUndoStack().push(new CSMWorld::AddNestedCommand(*(mModel->model()), mModel->getParentId(), - selectionModel()->selectedRows().size(), + row, mModel->getParentColumn())); } diff --git a/apps/opencs/view/world/pathgridcreator.cpp b/apps/opencs/view/world/pathgridcreator.cpp new file mode 100644 index 0000000000..a305b1249d --- /dev/null +++ b/apps/opencs/view/world/pathgridcreator.cpp @@ -0,0 +1,29 @@ +#include "pathgridcreator.hpp" + +#include "../../model/world/data.hpp" + +CSVWorld::PathgridCreator::PathgridCreator( + CSMWorld::Data& data, + QUndoStack& undoStack, + const CSMWorld::UniversalId& id, + bool relaxedIdRules +) : GenericCreator(data, undoStack, id, relaxedIdRules) +{} + +std::string CSVWorld::PathgridCreator::getErrors() const +{ + std::string pathgridId = getId(); + + // Check user input for any errors. + std::string errors; + if (pathgridId.empty()) + { + errors = "No Pathgrid ID entered"; + } + else if (getData().getPathgrids().searchId(pathgridId) > -1) + { + errors = "Pathgrid with this ID already exists"; + } + + return errors; +} diff --git a/apps/opencs/view/world/pathgridcreator.hpp b/apps/opencs/view/world/pathgridcreator.hpp new file mode 100644 index 0000000000..10f64a0a76 --- /dev/null +++ b/apps/opencs/view/world/pathgridcreator.hpp @@ -0,0 +1,26 @@ +#ifndef PATHGRIDCREATOR_HPP +#define PATHGRIDCREATOR_HPP + +#include "genericcreator.hpp" + +namespace CSVWorld +{ + /// \brief Record creator for pathgrids. + class PathgridCreator : public GenericCreator + { + Q_OBJECT + + public: + + PathgridCreator( + CSMWorld::Data& data, + QUndoStack& undoStack, + const CSMWorld::UniversalId& id, + bool relaxedIdRules = false); + + /// \return Error description for current user input. + virtual std::string getErrors() const; + }; +} + +#endif // PATHGRIDCREATOR_HPP diff --git a/apps/opencs/view/world/referencecreator.cpp b/apps/opencs/view/world/referencecreator.cpp index 73ca62e025..cc7ae545a7 100644 --- a/apps/opencs/view/world/referencecreator.cpp +++ b/apps/opencs/view/world/referencecreator.cpp @@ -9,6 +9,7 @@ #include "../../model/world/columns.hpp" #include "../../model/world/idtable.hpp" #include "../../model/world/idcompletionmanager.hpp" +#include "../../model/world/commandmacro.hpp" #include "../widget/droplineedit.hpp" @@ -25,52 +26,6 @@ void CSVWorld::ReferenceCreator::configureCreateCommand (CSMWorld::CreateCommand findColumnIndex (CSMWorld::Columns::ColumnId_Cell); command.addValue (cellIdColumn, mCell->text()); - - // Set RefNum - int refNumColumn = dynamic_cast ( - *getData().getTableModel (CSMWorld::UniversalId::Type_References)). - findColumnIndex (CSMWorld::Columns::ColumnId_RefNum); - - command.addValue (refNumColumn, getRefNumCount()); -} - -void CSVWorld::ReferenceCreator::pushCommand (std::auto_ptr command, - const std::string& id) -{ - // get the old count - std::string cellId = mCell->text().toUtf8().constData(); - - CSMWorld::IdTable& cellTable = dynamic_cast ( - *getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); - - int countColumn = cellTable.findColumnIndex (CSMWorld::Columns::ColumnId_RefNumCounter); - - QModelIndex countIndex = cellTable.getModelIndex (cellId, countColumn); - - int count = cellTable.data (countIndex).toInt(); - - // command for incrementing counter - std::auto_ptr increment (new CSMWorld::ModifyCommand - (cellTable, countIndex, count+1)); - - getUndoStack().beginMacro (command->text()); - GenericCreator::pushCommand (command, id); - getUndoStack().push (increment.release()); - getUndoStack().endMacro(); -} - -int CSVWorld::ReferenceCreator::getRefNumCount() const -{ - std::string cellId = mCell->text().toUtf8().constData(); - - CSMWorld::IdTable& cellTable = dynamic_cast ( - *getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); - - int countColumn = cellTable.findColumnIndex (CSMWorld::Columns::ColumnId_RefNumCounter); - - QModelIndex countIndex = cellTable.getModelIndex (cellId, countColumn); - - return cellTable.data (countIndex).toInt(); } CSVWorld::ReferenceCreator::ReferenceCreator (CSMWorld::Data& data, QUndoStack& undoStack, @@ -87,6 +42,7 @@ CSVWorld::ReferenceCreator::ReferenceCreator (CSMWorld::Data& data, QUndoStack& setManualEditing (false); connect (mCell, SIGNAL (textChanged (const QString&)), this, SLOT (cellChanged())); + connect (mCell, SIGNAL (returnPressed()), this, SLOT (inputReturnPressed())); } void CSVWorld::ReferenceCreator::reset() @@ -147,11 +103,11 @@ void CSVWorld::ReferenceCreator::cloneMode(const std::string& originId, cellChanged(); //otherwise ok button will remain disabled } -CSVWorld::Creator *CSVWorld::ReferenceCreatorFactory::makeCreator (CSMDoc::Document& document, +CSVWorld::Creator *CSVWorld::ReferenceCreatorFactory::makeCreator (CSMDoc::Document& document, const CSMWorld::UniversalId& id) const { - return new ReferenceCreator(document.getData(), - document.getUndoStack(), + return new ReferenceCreator(document.getData(), + document.getUndoStack(), id, document.getIdCompletionManager()); } diff --git a/apps/opencs/view/world/referencecreator.hpp b/apps/opencs/view/world/referencecreator.hpp index c230d01268..31010fa24a 100644 --- a/apps/opencs/view/world/referencecreator.hpp +++ b/apps/opencs/view/world/referencecreator.hpp @@ -29,11 +29,6 @@ namespace CSVWorld virtual void configureCreateCommand (CSMWorld::CreateCommand& command) const; - virtual void pushCommand (std::auto_ptr command, - const std::string& id); - - int getRefNumCount() const; - public: ReferenceCreator (CSMWorld::Data& data, QUndoStack& undoStack, @@ -50,7 +45,7 @@ namespace CSVWorld /// Focus main input widget virtual void focus(); - + private slots: void cellChanged(); diff --git a/apps/opencs/view/world/regionmap.cpp b/apps/opencs/view/world/regionmap.cpp index 49764bd178..31e5d94ca8 100644 --- a/apps/opencs/view/world/regionmap.cpp +++ b/apps/opencs/view/world/regionmap.cpp @@ -17,6 +17,7 @@ #include "../../model/world/commands.hpp" #include "../../model/world/columns.hpp" #include "../../model/world/tablemimedata.hpp" +#include "../../model/world/commandmacro.hpp" void CSVWorld::RegionMap::contextMenuEvent (QContextMenuEvent *event) { @@ -159,8 +160,7 @@ void CSVWorld::RegionMap::setRegion (const std::string& regionId) QString regionId2 = QString::fromUtf8 (regionId.c_str()); - if (selected.size()>1) - mDocument.getUndoStack().beginMacro (tr ("Set Region")); + CSMWorld::CommandMacro macro (mDocument.getUndoStack(), selected.size()>1 ? tr ("Set Region") : ""); for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter) { @@ -170,12 +170,8 @@ void CSVWorld::RegionMap::setRegion (const std::string& regionId) QModelIndex index = cellsModel->getModelIndex (cellId, cellsModel->findColumnIndex (CSMWorld::Columns::ColumnId_Region)); - mDocument.getUndoStack().push ( - new CSMWorld::ModifyCommand (*cellsModel, index, regionId2)); + macro.push (new CSMWorld::ModifyCommand (*cellsModel, index, regionId2)); } - - if (selected.size()>1) - mDocument.getUndoStack().endMacro(); } CSVWorld::RegionMap::RegionMap (const CSMWorld::UniversalId& universalId, @@ -258,19 +254,15 @@ void CSVWorld::RegionMap::createCells() CSMWorld::IdTable *cellsModel = &dynamic_cast (* mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); - if (selected.size()>1) - mDocument.getUndoStack().beginMacro (tr ("Create cells")); + CSMWorld::CommandMacro macro (mDocument.getUndoStack(), selected.size()>1 ? tr ("Create cells"): ""); for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter) { std::string cellId = model()->data (*iter, CSMWorld::RegionMap::Role_CellId). toString().toUtf8().constData(); - mDocument.getUndoStack().push (new CSMWorld::CreateCommand (*cellsModel, cellId)); + macro.push (new CSMWorld::CreateCommand (*cellsModel, cellId)); } - - if (selected.size()>1) - mDocument.getUndoStack().endMacro(); } void CSVWorld::RegionMap::setRegion() @@ -311,7 +303,7 @@ void CSVWorld::RegionMap::view() hint << cellId; } - emit editRequest (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Scene, "sys::default"), + emit editRequest (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Scene, ESM::CellId::sDefaultWorldspace), hint.str()); } diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index 44fe94d840..c84818b373 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -38,7 +38,7 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D CSVRender::WorldspaceWidget* worldspaceWidget = NULL; widgetType whatWidget; - if (id.getId()=="sys::default") + if (id.getId()==ESM::CellId::sDefaultWorldspace) { whatWidget = widget_Paged; @@ -112,7 +112,7 @@ CSVWidget::SceneToolbar* CSVWorld::SceneSubView::makeToolbar (CSVRender::Worldsp if (type==widget_Paged) { - CSVWidget::SceneToolToggle *controlVisibilityTool = + CSVWidget::SceneToolToggle2 *controlVisibilityTool = dynamic_cast (*widget). makeControlVisibilitySelector (toolbar); @@ -122,7 +122,7 @@ CSVWidget::SceneToolbar* CSVWorld::SceneSubView::makeToolbar (CSVRender::Worldsp CSVWidget::SceneToolRun *runTool = widget->makeRunTool (toolbar); toolbar->addTool (runTool); - toolbar->addTool (widget->makeEditModeSelector (toolbar)); + toolbar->addTool (widget->makeEditModeSelector (toolbar), runTool); return toolbar; } @@ -160,7 +160,7 @@ void CSVWorld::SceneSubView::cellSelectionChanged (const CSMWorld::UniversalId& void CSVWorld::SceneSubView::cellSelectionChanged (const CSMWorld::CellSelection& selection) { - setUniversalId(CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Scene, "sys::default")); + setUniversalId(CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Scene, ESM::CellId::sDefaultWorldspace)); int size = selection.getSize(); std::ostringstream stream; diff --git a/apps/opencs/view/world/scriptedit.cpp b/apps/opencs/view/world/scriptedit.cpp index 9f1abcf970..6f27d56569 100644 --- a/apps/opencs/view/world/scriptedit.cpp +++ b/apps/opencs/view/world/scriptedit.cpp @@ -38,21 +38,22 @@ bool CSVWorld::ScriptEdit::event (QEvent *event) return QPlainTextEdit::event (event); } -CSVWorld::ScriptEdit::ScriptEdit (const CSMDoc::Document& document, ScriptHighlighter::Mode mode, - QWidget* parent) - : QPlainTextEdit (parent), - mChangeLocked (0), +CSVWorld::ScriptEdit::ScriptEdit( + const CSMDoc::Document& document, + ScriptHighlighter::Mode mode, + QWidget* parent +) : QPlainTextEdit(parent), + mChangeLocked(0), mShowLineNum(false), mLineNumberArea(0), mDefaultFont(font()), mMonoFont(QFont("Monospace")), - mDocument (document), + mTabCharCount(4), + mDocument(document), mWhiteListQoutes("^[a-z|_]{1}[a-z|0-9|_]{0,}$", Qt::CaseInsensitive) - { -// setAcceptRichText (false); - setLineWrapMode (QPlainTextEdit::NoWrap); - setTabStopWidth (4); + wrapLines(false); + setTabWidth(); setUndoRedoEnabled (false); // we use OpenCS-wide undo/redo instead mAllowedTypes <settingChanged (setting)) + // Set tab width to specified number of characters using current font. + setTabStopWidth(mTabCharCount * fontMetrics().width(' ')); +} + +void CSVWorld::ScriptEdit::wrapLines(bool wrap) +{ + if (wrap) + { + setLineWrapMode(QPlainTextEdit::WidgetWidth); + } + else + { + setLineWrapMode(QPlainTextEdit::NoWrap); + } +} + +void CSVWorld::ScriptEdit::settingChanged(const CSMPrefs::Setting *setting) +{ + // Determine which setting was changed. + if (mHighlighter->settingChanged(setting)) + { updateHighlighting(); - else if (*setting=="Scripts/mono-font") - setFont (setting->isTrue() ? mMonoFont : mDefaultFont); - else if (*setting=="Scripts/show-linenum") - showLineNum (setting->isTrue()); + } + else if (*setting == "Scripts/mono-font") + { + setFont(setting->isTrue() ? mMonoFont : mDefaultFont); + setTabWidth(); + } + else if (*setting == "Scripts/show-linenum") + { + showLineNum(setting->isTrue()); + } + else if (*setting == "Scripts/wrap-lines") + { + wrapLines(setting->isTrue()); + } + else if (*setting == "Scripts/tab-width") + { + mTabCharCount = setting->toInt(); + setTabWidth(); + } } void CSVWorld::ScriptEdit::idListChanged() diff --git a/apps/opencs/view/world/scriptedit.hpp b/apps/opencs/view/world/scriptedit.hpp index 941a6295d8..4977ed8e07 100644 --- a/apps/opencs/view/world/scriptedit.hpp +++ b/apps/opencs/view/world/scriptedit.hpp @@ -22,6 +22,7 @@ namespace CSVWorld { class LineNumberArea; + /// \brief Editor for scripts. class ScriptEdit : public QPlainTextEdit { Q_OBJECT @@ -52,6 +53,7 @@ namespace CSVWorld LineNumberArea *mLineNumberArea; QFont mDefaultFont; QFont mMonoFont; + int mTabCharCount; protected: @@ -70,13 +72,13 @@ namespace CSVWorld void lineNumberAreaPaintEvent(QPaintEvent *event); int lineNumberAreaWidth(); void showLineNum(bool show); - void setMonoFont(bool show); protected: virtual void resizeEvent(QResizeEvent *e); private: + QVector mAllowedTypes; const CSMDoc::Document& mDocument; const QRegExp mWhiteListQoutes; @@ -89,9 +91,18 @@ namespace CSVWorld bool stringNeedsQuote(const std::string& id) const; + /// \brief Set tab width for script editor. + void setTabWidth(); + + /// \brief Turn line wrapping in script editor on or off. + /// \param wrap Whether or not to wrap lines. + void wrapLines(bool wrap); + private slots: - void settingChanged (const CSMPrefs::Setting *setting); + /// \brief Update editor when related setting is changed. + /// \param setting Setting that was changed. + void settingChanged(const CSMPrefs::Setting *setting); void idListChanged(); diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index ee0acb560a..c9b8127f68 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -324,7 +324,9 @@ void CSVWorld::ScriptSubView::switchToRow (int row) std::string id = mModel->data (mModel->index (row, idColumn)).toString().toUtf8().constData(); setUniversalId (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Script, id)); - mEditor->setPlainText (mModel->data (mModel->index (row, mColumn)).toString()); + bool oldSignalsState = mEditor->blockSignals( true ); + mEditor->setPlainText( mModel->data(mModel->index(row, mColumn)).toString() ); + mEditor->blockSignals( oldSignalsState ); std::vector selection (1, id); mCommandDispatcher.setSelection (selection); diff --git a/apps/opencs/view/world/startscriptcreator.cpp b/apps/opencs/view/world/startscriptcreator.cpp index 69b1b3ff12..8911990271 100644 --- a/apps/opencs/view/world/startscriptcreator.cpp +++ b/apps/opencs/view/world/startscriptcreator.cpp @@ -1,20 +1,110 @@ #include "startscriptcreator.hpp" -CSVWorld::StartScriptCreator::StartScriptCreator(CSMWorld::Data &data, QUndoStack &undoStack, const CSMWorld::UniversalId &id, bool relaxedIdRules): - GenericCreator (data, undoStack, id, true) -{} +#include + +#include "../../model/doc/document.hpp" + +#include "../../model/world/columns.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/data.hpp" +#include "../../model/world/idcompletionmanager.hpp" +#include "../../model/world/idtable.hpp" + +#include "../widget/droplineedit.hpp" + +std::string CSVWorld::StartScriptCreator::getId() const +{ + return mScript->text().toUtf8().constData(); +} + +CSMWorld::IdTable& CSVWorld::StartScriptCreator::getStartScriptsTable() const +{ + return dynamic_cast ( + *getData().getTableModel(getCollectionId()) + ); +} + +CSVWorld::StartScriptCreator::StartScriptCreator( + CSMWorld::Data &data, + QUndoStack &undoStack, + const CSMWorld::UniversalId &id, + CSMWorld::IdCompletionManager& completionManager +) : GenericCreator(data, undoStack, id, true) +{ + setManualEditing(false); + + // Add script ID input label. + QLabel *label = new QLabel("Script ID", this); + insertBeforeButtons(label, false); + + // Add script ID input with auto-completion. + CSMWorld::ColumnBase::Display displayType = CSMWorld::ColumnBase::Display_Script; + mScript = new CSVWidget::DropLineEdit(displayType, this); + mScript->setCompleter(completionManager.getCompleter(displayType).get()); + insertBeforeButtons(mScript, true); + + connect(mScript, SIGNAL (textChanged(const QString&)), this, SLOT (scriptChanged())); + connect(mScript, SIGNAL (returnPressed()), this, SLOT (inputReturnPressed())); +} + +void CSVWorld::StartScriptCreator::cloneMode( + const std::string& originId, + const CSMWorld::UniversalId::Type type) +{ + CSVWorld::GenericCreator::cloneMode(originId, type); + + // Look up cloned record in start scripts table and set script ID text. + CSMWorld::IdTable& table = getStartScriptsTable(); + int column = table.findColumnIndex(CSMWorld::Columns::ColumnId_Id); + mScript->setText(table.data(table.getModelIndex(originId, column)).toString()); +} std::string CSVWorld::StartScriptCreator::getErrors() const { - std::string errors; + std::string scriptId = getId(); - errors = getIdValidatorResult(); - if (errors.length() > 0) - return errors; - else if (getData().getScripts().searchId(getId()) == -1) + // Check user input for any errors. + std::string errors; + if (scriptId.empty()) + { + errors = "No Script ID entered"; + } + else if (getData().getScripts().searchId(scriptId) == -1) + { errors = "Script ID not found"; - else if (getData().getStartScripts().searchId(getId()) > -1 ) + } + else if (getData().getStartScripts().searchId(scriptId) > -1) + { errors = "Script with this ID already registered as Start Script"; + } return errors; } + +void CSVWorld::StartScriptCreator::focus() +{ + mScript->setFocus(); +} + +void CSVWorld::StartScriptCreator::reset() +{ + CSVWorld::GenericCreator::reset(); + mScript->setText(""); +} + +void CSVWorld::StartScriptCreator::scriptChanged() +{ + update(); +} + +CSVWorld::Creator *CSVWorld::StartScriptCreatorFactory::makeCreator( + CSMDoc::Document& document, + const CSMWorld::UniversalId& id) const +{ + return new StartScriptCreator( + document.getData(), + document.getUndoStack(), + id, + document.getIdCompletionManager() + ); +} diff --git a/apps/opencs/view/world/startscriptcreator.hpp b/apps/opencs/view/world/startscriptcreator.hpp index 07fe8ff3d3..72eb67bccd 100644 --- a/apps/opencs/view/world/startscriptcreator.hpp +++ b/apps/opencs/view/world/startscriptcreator.hpp @@ -3,23 +3,73 @@ #include "genericcreator.hpp" -namespace CSVWorld { +namespace CSMWorld +{ + class IdCompletionManager; + class IdTable; +} +namespace CSVWidget +{ + class DropLineEdit; +} + +namespace CSVWorld +{ + /// \brief Record creator for start scripts. class StartScriptCreator : public GenericCreator { Q_OBJECT - public: - StartScriptCreator(CSMWorld::Data& data, QUndoStack& undoStack, - const CSMWorld::UniversalId& id, bool relaxedIdRules = false); + CSVWidget::DropLineEdit *mScript; + private: + + /// \return script ID entered by user. + virtual std::string getId() const; + + /// \return reference to table containing start scripts. + CSMWorld::IdTable& getStartScriptsTable() const; + + public: + + StartScriptCreator( + CSMWorld::Data& data, + QUndoStack& undoStack, + const CSMWorld::UniversalId& id, + CSMWorld::IdCompletionManager& completionManager); + + /// \brief Set script ID input widget to ID of record to be cloned. + /// \param originId Script ID to be cloned. + /// \param type Type of record to be cloned. + virtual void cloneMode( + const std::string& originId, + const CSMWorld::UniversalId::Type type); + + /// \return Error description for current user input. virtual std::string getErrors() const; - ///< Return formatted error descriptions for the current state of the creator. if an empty - /// string is returned, there is no error. + + /// \brief Set focus to script ID input widget. + virtual void focus(); + + /// \brief Clear script ID input widget. + virtual void reset(); + + private slots: + + /// \brief Check user input for any errors. + void scriptChanged(); }; + /// \brief Creator factory for start script record creator. + class StartScriptCreatorFactory : public CreatorFactoryBase + { + public: + + virtual Creator *makeCreator( + CSMDoc::Document& document, + const CSMWorld::UniversalId& id) const; + }; } - - #endif // STARTSCRIPTCREATOR_HPP diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index 88375caafc..69c0c01cce 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -14,6 +14,7 @@ #include "scenesubview.hpp" #include "dialoguecreator.hpp" #include "infocreator.hpp" +#include "pathgridcreator.hpp" #include "previewsubview.hpp" void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) @@ -42,7 +43,6 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) CSMWorld::UniversalId::Type_Enchantments, CSMWorld::UniversalId::Type_BodyParts, CSMWorld::UniversalId::Type_SoundGens, - CSMWorld::UniversalId::Type_Pathgrids, CSMWorld::UniversalId::Type_None // end marker }; @@ -52,7 +52,7 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) new CSVDoc::SubViewFactoryWithCreator >); manager.add (CSMWorld::UniversalId::Type_StartScripts, - new CSVDoc::SubViewFactoryWithCreator >); + new CSVDoc::SubViewFactoryWithCreator); manager.add (CSMWorld::UniversalId::Type_Cells, new CSVDoc::SubViewFactoryWithCreator >); @@ -75,6 +75,9 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) manager.add (CSMWorld::UniversalId::Type_JournalInfos, new CSVDoc::SubViewFactoryWithCreator); + manager.add (CSMWorld::UniversalId::Type_Pathgrids, + new CSVDoc::SubViewFactoryWithCreator >); + // Subviews for resources tables manager.add (CSMWorld::UniversalId::Type_Meshes, new CSVDoc::SubViewFactoryWithCreator); @@ -125,7 +128,6 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) CSMWorld::UniversalId::Type_Enchantment, CSMWorld::UniversalId::Type_BodyPart, CSMWorld::UniversalId::Type_SoundGen, - CSMWorld::UniversalId::Type_Pathgrid, CSMWorld::UniversalId::Type_None // end marker }; @@ -136,8 +138,7 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) CreatorFactory > (false)); manager.add (CSMWorld::UniversalId::Type_StartScript, - new CSVDoc::SubViewFactoryWithCreator > (false)); + new CSVDoc::SubViewFactoryWithCreator(false)); manager.add (CSMWorld::UniversalId::Type_Skill, new CSVDoc::SubViewFactoryWithCreator (false)); @@ -169,6 +170,9 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) manager.add (CSMWorld::UniversalId::Type_Journal, new CSVDoc::SubViewFactoryWithCreator (false)); + manager.add (CSMWorld::UniversalId::Type_Pathgrid, + new CSVDoc::SubViewFactoryWithCreator > (false)); + manager.add (CSMWorld::UniversalId::Type_DebugProfile, new CSVDoc::SubViewFactoryWithCreator > (false)); @@ -177,7 +181,7 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) manager.add (CSMWorld::UniversalId::Type_MetaData, new CSVDoc::SubViewFactory); - + //preview manager.add (CSMWorld::UniversalId::Type_Preview, new CSVDoc::SubViewFactory); } diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 95dfa10340..45e50dba7b 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -12,7 +11,6 @@ #include "../../model/doc/document.hpp" -#include "../../model/world/data.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/infotableproxymodel.hpp" #include "../../model/world/idtableproxymodel.hpp" @@ -20,13 +18,10 @@ #include "../../model/world/idtable.hpp" #include "../../model/world/record.hpp" #include "../../model/world/columns.hpp" -#include "../../model/world/tablemimedata.hpp" -#include "../../model/world/tablemimedata.hpp" #include "../../model/world/commanddispatcher.hpp" #include "../../model/prefs/state.hpp" -#include "recordstatusdelegate.hpp" #include "tableeditidaction.hpp" #include "util.hpp" @@ -231,7 +226,7 @@ void CSVWorld::Table::mouseDoubleClickEvent (QMouseEvent *event) CSVWorld::Table::Table (const CSMWorld::UniversalId& id, bool createAndDelete, bool sorting, CSMDoc::Document& document) : DragRecordTable(document), mCreateAction (0), - mCloneAction(0),mRecordStatusDisplay (0) + mCloneAction(0), mRecordStatusDisplay (0), mJumpToAddedRecord(false), mUnselectAfterJump(false) { mModel = &dynamic_cast (*mDocument.getData().getTableModel (id)); @@ -339,8 +334,6 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, connect (mProxyModel, SIGNAL (rowsRemoved (const QModelIndex&, int, int)), this, SLOT (tableSizeUpdate())); - //connect (mProxyModel, SIGNAL (rowsInserted (const QModelIndex&, int, int)), - // this, SLOT (rowsInsertedEvent(const QModelIndex&, int, int))); connect (mProxyModel, SIGNAL (rowAdded (const std::string &)), this, SLOT (rowAdded (const std::string &))); diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index 53249f66fe..768ff185d5 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -11,7 +11,6 @@ #include "../../model/world/universalid.hpp" #include "dragrecordtable.hpp" -class QUndoStack; class QAction; namespace CSMDoc @@ -21,7 +20,6 @@ namespace CSMDoc namespace CSMWorld { - class Data; class IdTableProxyModel; class IdTableBase; class CommandDispatcher; diff --git a/apps/opencs/view/world/tablebottombox.cpp b/apps/opencs/view/world/tablebottombox.cpp index eed522227e..5a25bbc53e 100644 --- a/apps/opencs/view/world/tablebottombox.cpp +++ b/apps/opencs/view/world/tablebottombox.cpp @@ -76,7 +76,7 @@ CSVWorld::TableBottomBox::TableBottomBox (const CreatorFactoryBase& creatorFacto CSMDoc::Document& document, const CSMWorld::UniversalId& id, QWidget *parent) -: QWidget (parent), mShowStatusBar (false), mEditMode(EditMode_None), mHasPosition(false) +: QWidget (parent), mShowStatusBar (false), mEditMode(EditMode_None), mHasPosition(false), mRow(0), mColumn(0) { for (int i=0; i<4; ++i) mStatusCount[i] = 0; diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index b0431f71a0..e44606652b 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -212,6 +212,13 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO return sb; } + case CSMWorld::ColumnBase::Display_UnsignedInteger8: + { + DialogueSpinBox *sb = new DialogueSpinBox(parent); + sb->setRange(0, UCHAR_MAX); + return sb; + } + case CSMWorld::ColumnBase::Display_Var: return new QLineEdit(parent); diff --git a/apps/opencs/view/world/vartypedelegate.cpp b/apps/opencs/view/world/vartypedelegate.cpp index a63e6028c2..8aa43d15b0 100644 --- a/apps/opencs/view/world/vartypedelegate.cpp +++ b/apps/opencs/view/world/vartypedelegate.cpp @@ -4,6 +4,7 @@ #include "../../model/world/commands.hpp" #include "../../model/world/columns.hpp" +#include "../../model/world/commandmacro.hpp" void CSVWorld::VarTypeDelegate::addCommands (QAbstractItemModel *model, const QModelIndex& index, int type) const @@ -36,13 +37,10 @@ void CSVWorld::VarTypeDelegate::addCommands (QAbstractItemModel *model, const QM default: break; // ignore the rest } - getUndoStack().beginMacro ( - "Modify " + model->headerData (index.column(), Qt::Horizontal, Qt::DisplayRole).toString()); + CSMWorld::CommandMacro macro (getUndoStack(), "Modify " + model->headerData (index.column(), Qt::Horizontal, Qt::DisplayRole).toString()); - getUndoStack().push (new CSMWorld::ModifyCommand (*model, index, type)); - getUndoStack().push (new CSMWorld::ModifyCommand (*model, next, value)); - - getUndoStack().endMacro(); + macro.push (new CSMWorld::ModifyCommand (*model, index, type)); + macro.push (new CSMWorld::ModifyCommand (*model, next, value)); } CSVWorld::VarTypeDelegate::VarTypeDelegate (const std::vector >& values, diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 0113fed022..f7a9fea3c2 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -67,6 +67,7 @@ add_openmw_dir (mwworld actionequip timestamp actionalchemy cellstore actionapply actioneat store esmstore recordcmp fallback actionrepair actionsoulgem livecellref actiondoor contentloader esmloader actiontrap cellreflist cellref physicssystem weather projectilemanager + cellpreloader ) add_openmw_dir (mwphysics @@ -75,7 +76,7 @@ add_openmw_dir (mwphysics add_openmw_dir (mwclass classes activator creature npc weapon armor potion apparatus book clothing container door - ingredient creaturelevlist itemlevlist light lockpick misc probe repair static actor + ingredient creaturelevlist itemlevlist light lockpick misc probe repair static actor bodypart ) add_openmw_dir (mwmechanics @@ -83,7 +84,7 @@ add_openmw_dir (mwmechanics drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aiescort aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning - character actors objects aistate coordinateconverter + character actors objects aistate coordinateconverter trading ) add_openmw_dir (mwstate @@ -114,7 +115,7 @@ endif () # Sound stuff - here so CMake doesn't stupidly recompile EVERYTHING # when we change the backend. include_directories( - ${FFMPEG_INCLUDE_DIRS} + ${FFmpeg_INCLUDE_DIRS} ) target_link_libraries(openmw @@ -130,8 +131,7 @@ target_link_libraries(openmw ${Boost_FILESYSTEM_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${OPENAL_LIBRARY} - ${FFMPEG_LIBRARIES} - ${BULLET_LIBRARIES} + ${FFmpeg_LIBRARIES} ${MYGUI_LIBRARIES} ${SDL2_LIBRARY} "osg-ffmpeg-videoplayer" @@ -165,7 +165,7 @@ if (ANDROID) endif (ANDROID) if (USE_SYSTEM_TINYXML) - target_link_libraries(openmw ${TINYXML_LIBRARIES}) + target_link_libraries(openmw ${TinyXML_LIBRARIES}) endif() if (NOT UNIX) @@ -182,7 +182,7 @@ if(APPLE) find_library(IOKIT_FRAMEWORK IOKit) target_link_libraries(openmw ${COCOA_FRAMEWORK} ${IOKIT_FRAMEWORK}) - if (FFMPEG_FOUND) + if (FFmpeg_FOUND) find_library(COREVIDEO_FRAMEWORK CoreVideo) find_library(VDA_FRAMEWORK VideoDecodeAcceleration) target_link_libraries(openmw ${COREVIDEO_FRAMEWORK} ${VDA_FRAMEWORK}) diff --git a/apps/openmw/crashcatcher.cpp b/apps/openmw/crashcatcher.cpp index 0b4ff6304f..4f0356259a 100644 --- a/apps/openmw/crashcatcher.cpp +++ b/apps/openmw/crashcatcher.cpp @@ -24,7 +24,7 @@ #ifndef PR_SET_PTRACER #define PR_SET_PTRACER 0x59616d61 #endif -#elif defined (__APPLE__) +#elif defined (__APPLE__) || defined (__FreeBSD__) #include #endif @@ -149,7 +149,7 @@ static void gdb_info(pid_t pid) "info registers\n" "shell echo \"\"\n" "shell echo \"* Backtrace\"\n" - "thread apply all backtrace full\n" + "thread apply all backtrace full 1000\n" "detach\n" "quit\n", pid); fclose(f); diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index b43fd2f530..62457cae6d 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -20,7 +20,7 @@ #include #include -#include +#include #include @@ -69,17 +69,13 @@ void OMW::Engine::executeLocalScripts() MWWorld::LocalScripts& localScripts = mEnvironment.getWorld()->getLocalScripts(); localScripts.startIteration(); - - while (!localScripts.isFinished()) + std::pair script; + while (localScripts.getNext(script)) { - std::pair script = localScripts.getNext(); - MWScript::InterpreterContext interpreterContext ( &script.second.getRefData().getLocals(), script.second); mEnvironment.getScriptManager()->run (script.first, interpreterContext); } - - localScripts.setIgnore (MWWorld::Ptr()); } void OMW::Engine::frame(float frametime) @@ -450,13 +446,12 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true); mResourceSystem.reset(new Resource::ResourceSystem(mVFS.get())); - mResourceSystem->getTextureManager()->setUnRefImageDataAfterApply(true); - mResourceSystem->getTextureManager()->setFilterSettings( + mResourceSystem->getSceneManager()->setUnRefImageDataAfterApply(false); // keep to Off for now to allow better state sharing + mResourceSystem->getSceneManager()->setFilterSettings( Settings::Manager::getString("texture mag filter", "General"), Settings::Manager::getString("texture min filter", "General"), Settings::Manager::getString("texture mipmap", "General"), - Settings::Manager::getInt("anisotropy", "General"), - NULL + Settings::Manager::getInt("anisotropy", "General") ); // Create input and UI first to set up a bootstrapping environment for @@ -474,8 +469,8 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) } // find correct path to the game controller bindings - const std::string localdefault = mCfgMgr.getLocalPath().string() + "/gamecontrollerdb.cfg"; - const std::string globaldefault = mCfgMgr.getGlobalPath().string() + "/gamecontrollerdb.cfg"; + const std::string localdefault = mCfgMgr.getLocalPath().string() + "/gamecontrollerdb.txt"; + const std::string globaldefault = mCfgMgr.getGlobalPath().string() + "/gamecontrollerdb.txt"; std::string gameControllerdb; if (boost::filesystem::exists(localdefault)) gameControllerdb = localdefault; @@ -653,6 +648,8 @@ void OMW::Engine::go() } else if (!mSkipMenu) { + mEnvironment.getWorld()->preloadCommonAssets(); + // start in main menu mEnvironment.getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); try diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index c3f0f8688e..a4bf1fe5ba 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -52,39 +53,8 @@ inline boost::filesystem::path lexical_cast mMap; -}; - -void validate(boost::any &v, std::vector const &tokens, FallbackMap*, int) -{ - if(v.empty()) - { - v = boost::any(FallbackMap()); - } - - FallbackMap *map = boost::any_cast(&v); - - for(std::vector::const_iterator it=tokens.begin(); it != tokens.end(); ++it) - { - int sep = it->find(","); - if(sep < 1 || sep == (int)it->length()-1) -#if (BOOST_VERSION < 104200) - throw boost::program_options::validation_error("invalid value"); -#else - throw boost::program_options::validation_error(boost::program_options::validation_error::invalid_option_value); -#endif - - std::string key(it->substr(0,sep)); - std::string value(it->substr(sep+1)); - - if(map->mMap.find(key) == map->mMap.end()) - { - map->mMap.insert(std::make_pair (key,value)); - } - } -} +using namespace Fallback; /** * \brief Parses application command line and calls \ref Cfg::ConfigurationManager diff --git a/apps/openmw/mwbase/journal.hpp b/apps/openmw/mwbase/journal.hpp index 738014ba6e..cd87928903 100644 --- a/apps/openmw/mwbase/journal.hpp +++ b/apps/openmw/mwbase/journal.hpp @@ -50,8 +50,9 @@ namespace MWBase virtual ~Journal() {} - virtual void addEntry (const std::string& id, int index) = 0; + virtual void addEntry (const std::string& id, int index, const MWWorld::Ptr& actor) = 0; ///< Add a journal entry. + /// @param actor Used as context for replacing of escape sequences (%name, etc). virtual void setJournalIndex (const std::string& id, int index) = 0; ///< Set the journal index without adding an entry. diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 42da1a052a..a0e9da26ac 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -97,7 +97,7 @@ namespace MWBase virtual int getBarterOffer(const MWWorld::Ptr& ptr,int basePrice, bool buying) = 0; ///< This is used by every service to determine the price of objects given the trading skills of the player and NPC. - virtual int getDerivedDisposition(const MWWorld::Ptr& ptr) = 0; + virtual int getDerivedDisposition(const MWWorld::Ptr& ptr, bool addTemporaryDispositionChange = true) = 0; ///< Calculate the diposition of an NPC toward the player. virtual int countDeaths (const std::string& id) const = 0; @@ -153,8 +153,7 @@ namespace MWBase PT_Bribe100, PT_Bribe1000 }; - virtual void getPersuasionDispositionChange (const MWWorld::Ptr& npc, PersuasionType type, - float currentTemporaryDispositionDelta, bool& success, float& tempChange, float& permChange) = 0; + virtual void getPersuasionDispositionChange (const MWWorld::Ptr& npc, PersuasionType type, bool& success, float& tempChange, float& permChange) = 0; ///< Perform a persuasion action on NPC virtual void forceStateUpdate(const MWWorld::Ptr &ptr) = 0; @@ -194,6 +193,8 @@ namespace MWBase /** ie AiCombat is active and the target is the actor **/ virtual std::list getActorsFighting(const MWWorld::Ptr& actor) = 0; + virtual std::list getEnemiesNearby(const MWWorld::Ptr& actor) = 0; + virtual void playerLoaded() = 0; virtual int countSavedGameRecords() const = 0; @@ -228,6 +229,8 @@ namespace MWBase /// Sets the NPC's Acrobatics skill to match the fWerewolfAcrobatics GMST. /// It only applies to the current form the NPC is in. virtual void applyWerewolfAcrobatics(const MWWorld::Ptr& actor) = 0; + + virtual void cleanupSummonedCreature(const MWWorld::Ptr& caster, int creatureActorId) = 0; }; } diff --git a/apps/openmw/mwbase/statemanager.hpp b/apps/openmw/mwbase/statemanager.hpp index 79ddbfa2ab..48a95f029f 100644 --- a/apps/openmw/mwbase/statemanager.hpp +++ b/apps/openmw/mwbase/statemanager.hpp @@ -78,8 +78,8 @@ namespace MWBase /** Used for quickload **/ virtual void quickLoad()=0; - virtual MWState::Character *getCurrentCharacter (bool create = true) = 0; - ///< \param create Create a new character, if there is no current character. + virtual MWState::Character *getCurrentCharacter () = 0; + ///< @note May return null. virtual CharacterIterator characterBegin() = 0; ///< Any call to SaveGame and getCurrentCharacter can invalidate the returned diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index f364ada7ac..77d740b6f8 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -176,7 +176,7 @@ namespace MWBase virtual void updateSkillArea() = 0; ///< update display of skills, factions, birth sign, reputation and bounty - virtual void changeCell(MWWorld::CellStore* cell) = 0; + virtual void changeCell(const MWWorld::CellStore* cell) = 0; ///< change the active cell virtual void setFocusObject(const MWWorld::Ptr& focus) = 0; @@ -353,8 +353,8 @@ namespace MWBase virtual std::string correctIconPath(const std::string& path) = 0; virtual std::string correctBookartPath(const std::string& path, int width, int height) = 0; virtual std::string correctTexturePath(const std::string& path) = 0; + virtual bool textureExists(const std::string& path) = 0; - virtual void requestMap(std::set cells) = 0; virtual void removeCell(MWWorld::CellStore* cell) = 0; virtual void writeFog(MWWorld::CellStore* cell) = 0; }; diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index ebce2e1bf8..7376e6b51e 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -56,7 +56,6 @@ namespace MWMechanics namespace MWWorld { - class Fallback; class CellStore; class Player; class LocalScripts; @@ -67,6 +66,11 @@ namespace MWWorld typedef std::vector > PtrMovementList; } +namespace Fallback +{ + class Map; +} + namespace MWBase { /// \brief Interface for the World (implemented in MWWorld) @@ -91,6 +95,8 @@ namespace MWBase virtual ~World() {} + virtual void preloadCommonAssets() = 0; + virtual void startNewGame (bool bypass) = 0; ///< \param bypass Bypass regular game start. @@ -119,7 +125,7 @@ namespace MWBase virtual void adjustSky() = 0; - virtual const MWWorld::Fallback *getFallback () const = 0; + virtual const Fallback::Map *getFallback () const = 0; virtual MWWorld::Player& getPlayer() = 0; virtual MWWorld::Ptr getPlayerPtr() = 0; @@ -137,7 +143,7 @@ namespace MWBase virtual bool isCellQuasiExterior() const = 0; - virtual osg::Vec2f getNorthVector (MWWorld::CellStore* cell) = 0; + virtual osg::Vec2f getNorthVector (const MWWorld::CellStore* cell) = 0; ///< get north vector for given interior cell virtual void getDoorMarkers (MWWorld::CellStore* cell, std::vector& out) = 0; @@ -225,15 +231,16 @@ namespace MWBase virtual float getTimeScaleFactor() const = 0; - virtual void changeToInteriorCell (const std::string& cellName, - const ESM::Position& position) = 0; + virtual void changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent=true) = 0; ///< Move to interior cell. + ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes - virtual void changeToExteriorCell (const ESM::Position& position) = 0; + virtual void changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent=true) = 0; ///< Move to exterior cell. + ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes - virtual void changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool detectWorldSpaceChange=true) = 0; - ///< @param detectWorldSpaceChange if true, clean up worldspace-specific data when the world space changes + virtual void changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent=true) = 0; + ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes virtual const ESM::Cell *getExterior (const std::string& cellName) const = 0; ///< Return a cell matching the given name or a 0-pointer, if there is no such cell. @@ -264,15 +271,19 @@ namespace MWBase virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, float x, float y, float z) = 0; ///< @return an updated Ptr in case the Ptr's cell changes - virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z) = 0; + virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z, bool movePhysics=true) = 0; ///< @return an updated Ptr virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0; virtual void rotateObject(const MWWorld::Ptr& ptr,float x,float y,float z, bool adjust = false) = 0; - virtual MWWorld::Ptr safePlaceObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos) = 0; - ///< place an object in a "safe" location (ie not in the void, etc). + virtual MWWorld::Ptr placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos) = 0; + ///< Place an object. Makes a copy of the Ptr. + + virtual MWWorld::Ptr safePlaceObject (const MWWorld::ConstPtr& ptr, const MWWorld::ConstPtr& referenceObject, MWWorld::CellStore* referenceCell, int direction, float distance) = 0; + ///< Place an object in a safe place next to \a referenceObject. \a direction and \a distance specify the wanted placement + /// relative to \a referenceObject (but the object may be placed somewhere else if the wanted location is obstructed). virtual void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false) const = 0; @@ -417,7 +428,7 @@ namespace MWBase virtual bool getLOS(const MWWorld::ConstPtr& actor,const MWWorld::ConstPtr& targetActor) = 0; ///< get Line of Sight (morrowind stupid implementation) - virtual float getDistToNearestRayHit(const osg::Vec3f& from, const osg::Vec3f& dir, float maxDist) = 0; + virtual float getDistToNearestRayHit(const osg::Vec3f& from, const osg::Vec3f& dir, float maxDist, bool includeWater = false) = 0; virtual void enableActorCollision(const MWWorld::Ptr& actor, bool enable) = 0; @@ -485,7 +496,7 @@ namespace MWBase // Are we in an exterior or pseudo-exterior cell and it's night? virtual bool isDark() const = 0; - virtual bool findInteriorPositionInWorldSpace(MWWorld::CellStore* cell, osg::Vec3f& result) = 0; + virtual bool findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result) = 0; /// Teleports \a ptr to the closest reference of \a id (e.g. DivineMarker, PrisonMarker, TempleMarker) /// @note id must be lower case @@ -521,8 +532,8 @@ namespace MWBase virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos) = 0; - virtual void explodeSpell (const osg::Vec3f& origin, const ESM::EffectList& effects, - const MWWorld::Ptr& caster, ESM::RangeType rangeType, const std::string& id, const std::string& sourceName) = 0; + virtual void explodeSpell (const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, + const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id, const std::string& sourceName) = 0; virtual void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0; @@ -545,6 +556,8 @@ namespace MWBase virtual float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) = 0; virtual void removeContainerScripts(const MWWorld::Ptr& reference) = 0; + + virtual bool isPlayerInJail() const = 0; }; } diff --git a/apps/openmw/mwclass/bodypart.cpp b/apps/openmw/mwclass/bodypart.cpp new file mode 100644 index 0000000000..be2b927cc2 --- /dev/null +++ b/apps/openmw/mwclass/bodypart.cpp @@ -0,0 +1,52 @@ +#include "bodypart.hpp" + +#include "../mwrender/renderinginterface.hpp" +#include "../mwrender/objects.hpp" + +#include "../mwworld/cellstore.hpp" + +namespace MWClass +{ + + MWWorld::Ptr BodyPart::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const + { + const MWWorld::LiveCellRef *ref = ptr.get(); + + return MWWorld::Ptr(cell.insert(ref), &cell); + } + + void BodyPart::insertObjectRendering(const MWWorld::Ptr &ptr, const std::string &model, MWRender::RenderingInterface &renderingInterface) const + { + if (!model.empty()) { + renderingInterface.getObjects().insertModel(ptr, model); + } + } + + void BodyPart::insertObject(const MWWorld::Ptr &ptr, const std::string &model, MWPhysics::PhysicsSystem &physics) const + { + } + + std::string BodyPart::getName(const MWWorld::ConstPtr &ptr) const + { + return std::string(); + } + + void BodyPart::registerSelf() + { + boost::shared_ptr instance (new BodyPart); + + registerClass (typeid (ESM::BodyPart).name(), instance); + } + + std::string BodyPart::getModel(const MWWorld::ConstPtr &ptr) const + { + const MWWorld::LiveCellRef *ref = ptr.get(); + + const std::string &model = ref->mBase->mModel; + if (!model.empty()) { + return "meshes\\" + model; + } + return ""; + } + +} diff --git a/apps/openmw/mwclass/bodypart.hpp b/apps/openmw/mwclass/bodypart.hpp new file mode 100644 index 0000000000..79c7c860de --- /dev/null +++ b/apps/openmw/mwclass/bodypart.hpp @@ -0,0 +1,31 @@ +#ifndef GAME_MWCLASS_BODYPART_H +#define GAME_MWCLASS_BODYPART_H + +#include "../mwworld/class.hpp" + +namespace MWClass +{ + + class BodyPart : public MWWorld::Class + { + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; + + public: + + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; + ///< Add reference into a cell for rendering + + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; + + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; + ///< \return name (the one that is to be presented to the user; not the internal one); + /// can return an empty string. + + static void registerSelf(); + + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; + }; + +} + +#endif diff --git a/apps/openmw/mwclass/classes.cpp b/apps/openmw/mwclass/classes.cpp index c303b23af0..a552dfebf0 100644 --- a/apps/openmw/mwclass/classes.cpp +++ b/apps/openmw/mwclass/classes.cpp @@ -20,6 +20,7 @@ #include "probe.hpp" #include "repair.hpp" #include "static.hpp" +#include "bodypart.hpp" namespace MWClass { @@ -45,5 +46,6 @@ namespace MWClass Probe::registerSelf(); Repair::registerSelf(); Static::registerSelf(); + BodyPart::registerSelf(); } } diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index e9502d86b8..51c83eee1c 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -134,6 +134,9 @@ namespace MWClass data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee, ref->mBase->mAiData.mFlee); data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Alarm, ref->mBase->mAiData.mAlarm); + if (data->mCreatureStats.isDead()) + data->mCreatureStats.setDeathAnimationFinished(true); + // spells for (std::vector::const_iterator iter (ref->mBase->mSpells.mList.begin()); iter!=ref->mBase->mSpells.mList.end(); ++iter) @@ -182,6 +185,29 @@ namespace MWClass return ""; } + void Creature::getModelsToPreload(const MWWorld::Ptr &ptr, std::vector &models) const + { + std::string model = getModel(ptr); + if (!model.empty()) + models.push_back(model); + + // FIXME: use const version of InventoryStore functions once they are available + if (ptr.getClass().hasInventoryStore(ptr)) + { + MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr); + for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) + { + MWWorld::ContainerStoreIterator equipped = invStore.getSlot(slot); + if (equipped != invStore.end()) + { + model = equipped->getClass().getModel(*equipped); + if (!model.empty()) + models.push_back(model); + } + } + } + } + std::string Creature::getName (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); @@ -219,13 +245,10 @@ namespace MWClass MWMechanics::applyFatigueLoss(ptr, weapon, attackStrength); - // TODO: where is the distance defined? - float dist = 200.f; + float dist = gmst.find("fCombatDistance")->getFloat(); if (!weapon.isEmpty()) - { - const float fCombatDistance = gmst.find("fCombatDistance")->getFloat(); - dist = fCombatDistance * weapon.get()->mBase->mData.mReach; - } + dist *= weapon.get()->mBase->mData.mReach; + std::pair result = MWBase::Environment::get().getWorld()->getHitContact(ptr, dist); if (result.first.isEmpty()) return; // Didn't hit anything @@ -283,18 +306,7 @@ namespace MWClass } // Apply "On hit" enchanted weapons - std::string enchantmentName = !weapon.isEmpty() ? weapon.getClass().getEnchantment(weapon) : ""; - if (!enchantmentName.empty()) - { - const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( - enchantmentName); - if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) - { - MWMechanics::CastSpell cast(ptr, victim); - cast.mHitPosition = hitPosition; - cast.cast(weapon); - } - } + MWMechanics::applyOnStrikeEnchantment(ptr, victim, weapon, hitPosition); } else if (isBipedal(ptr)) { @@ -747,20 +759,31 @@ namespace MWClass void Creature::respawn(const MWWorld::Ptr &ptr) const { - if (isFlagBitSet(ptr, ESM::Creature::Respawn)) + const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); + if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead()) + return; + + const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->getFloat(); + static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->getFloat(); + + float delay = ptr.getRefData().getCount() == 0 ? fCorpseClearDelay : std::min(fCorpseRespawnDelay, fCorpseClearDelay); + + if (isFlagBitSet(ptr, ESM::Creature::Respawn) + && creatureStats.getTimeOfDeath() + delay <= MWBase::Environment::get().getWorld()->getTimeStamp()) { - // Note we do not respawn moved references in the cell they were moved to. Instead they are respawned in the original cell. - // This also means we cannot respawn dynamically placed references with no content file connection. if (ptr.getCellRef().hasContentFile()) { if (ptr.getRefData().getCount() == 0) ptr.getRefData().setCount(1); - // Reset to original position - ptr.getRefData().setPosition(ptr.getCellRef().getPosition()); - MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); ptr.getRefData().setCustomData(NULL); + + // Reset to original position + MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().pos[0], + ptr.getCellRef().getPosition().pos[1], + ptr.getCellRef().getPosition().pos[2]); } } } diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index cb89a53d62..bea56887a0 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -101,6 +101,9 @@ namespace MWClass virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; + virtual void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const; + ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: list getModel(). + virtual bool isActor() const { return true; diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index db2ba45bc7..e0e890b604 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -6,6 +6,7 @@ #include "../mwmechanics/levelledlist.hpp" #include "../mwworld/customdata.hpp" +#include "../mwmechanics/creaturestats.hpp" namespace MWClass { @@ -43,7 +44,28 @@ namespace MWClass ensureCustomData(ptr); CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData(); - customData.mSpawn = true; + if (customData.mSpawn) + return; + + MWWorld::Ptr creature = (customData.mSpawnActorId == -1) ? MWWorld::Ptr() : MWBase::Environment::get().getWorld()->searchPtrViaActorId(customData.mSpawnActorId); + if (!creature.isEmpty()) + { + const MWMechanics::CreatureStats& creatureStats = creature.getClass().getCreatureStats(creature); + if (creature.getRefData().getCount() == 0) + customData.mSpawn = true; + else if (creatureStats.isDead()) + { + const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->getFloat(); + static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->getFloat(); + + float delay = std::min(fCorpseRespawnDelay, fCorpseClearDelay); + if (creatureStats.getTimeOfDeath() + delay <= MWBase::Environment::get().getWorld()->getTimeStamp()) + customData.mSpawn = true; + } + } + else + customData.mSpawn = true; } void CreatureLevList::registerSelf() @@ -53,6 +75,24 @@ namespace MWClass registerClass (typeid (ESM::CreatureLevList).name(), instance); } + void CreatureLevList::getModelsToPreload(const MWWorld::Ptr &ptr, std::vector &models) const + { + // disable for now, too many false positives + /* + const MWWorld::LiveCellRef *ref = ptr.get(); + for (std::vector::const_iterator it = ref->mBase->mList.begin(); it != ref->mBase->mList.end(); ++it) + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + if (it->mLevel > player.getClass().getCreatureStats(player).getLevel()) + continue; + + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + MWWorld::ManualRef ref(store, it->mId); + ref.getPtr().getClass().getModelsToPreload(ref.getPtr(), models); + } + */ + } + void CreatureLevList::insertObjectRendering(const MWWorld::Ptr &ptr, const std::string& model, MWRender::RenderingInterface &renderingInterface) const { ensureCustomData(ptr); @@ -80,7 +120,7 @@ namespace MWClass const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); MWWorld::ManualRef ref(store, id); ref.getPtr().getCellRef().setPosition(ptr.getCellRef().getPosition()); - MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(), ptr.getCell() , ptr.getCellRef().getPosition()); + MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->placeObject(ref.getPtr(), ptr.getCell() , ptr.getCellRef().getPosition()); customData.mSpawnActorId = placed.getClass().getCreatureStats(placed).getActorId(); customData.mSpawn = false; } diff --git a/apps/openmw/mwclass/creaturelevlist.hpp b/apps/openmw/mwclass/creaturelevlist.hpp index 67a7858d87..25b5cbddfb 100644 --- a/apps/openmw/mwclass/creaturelevlist.hpp +++ b/apps/openmw/mwclass/creaturelevlist.hpp @@ -17,6 +17,9 @@ namespace MWClass static void registerSelf(); + virtual void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const; + ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: list getModel(). + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 57a6d088ab..52cd562d1e 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -37,6 +37,7 @@ #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" +#include "../mwrender/npcanimation.hpp" #include "../mwgui/tooltips.hpp" @@ -350,6 +351,8 @@ namespace MWClass data->mNpcStats.setNeedRecalcDynamicStats(true); } + if (data->mNpcStats.isDead()) + data->mNpcStats.setDeathAnimationFinished(true); // race powers const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get().find(ref->mBase->mRace); @@ -429,6 +432,91 @@ namespace MWClass return model; } + void Npc::getModelsToPreload(const MWWorld::Ptr &ptr, std::vector &models) const + { + const MWWorld::LiveCellRef *npc = ptr.get(); + const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().search(npc->mBase->mRace); + if(race && race->mData.mFlags & ESM::Race::Beast) + models.push_back("meshes\\base_animkna.nif"); + + // keep these always loaded just in case + models.push_back("meshes/xargonian_swimkna.nif"); + models.push_back("meshes/xbase_anim_female.nif"); + models.push_back("meshes/xbase_anim.nif"); + + if (!npc->mBase->mModel.empty()) + models.push_back("meshes/"+npc->mBase->mModel); + + if (!npc->mBase->mHead.empty()) + { + const ESM::BodyPart* head = MWBase::Environment::get().getWorld()->getStore().get().search(npc->mBase->mHead); + if (head) + models.push_back("meshes/"+head->mModel); + } + if (!npc->mBase->mHair.empty()) + { + const ESM::BodyPart* hair = MWBase::Environment::get().getWorld()->getStore().get().search(npc->mBase->mHair); + if (hair) + models.push_back("meshes/"+hair->mModel); + } + + bool female = (npc->mBase->mFlags & ESM::NPC::Female); + + // FIXME: use const version of InventoryStore functions once they are available + // preload equipped items + if (ptr.getClass().hasInventoryStore(ptr)) + { + MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr); + for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) + { + MWWorld::ContainerStoreIterator equipped = invStore.getSlot(slot); + if (equipped != invStore.end()) + { + std::vector parts; + if(equipped->getTypeName() == typeid(ESM::Clothing).name()) + { + const ESM::Clothing *clothes = equipped->get()->mBase; + parts = clothes->mParts.mParts; + } + else if(equipped->getTypeName() == typeid(ESM::Armor).name()) + { + const ESM::Armor *armor = equipped->get()->mBase; + parts = armor->mParts.mParts; + } + else + { + std::string model = equipped->getClass().getModel(*equipped); + if (!model.empty()) + models.push_back(model); + } + + for (std::vector::const_iterator it = parts.begin(); it != parts.end(); ++it) + { + std::string partname = female ? it->mFemale : it->mMale; + if (partname.empty()) + partname = female ? it->mMale : it->mFemale; + const ESM::BodyPart* part = MWBase::Environment::get().getWorld()->getStore().get().search(partname); + if (part && !part->mModel.empty()) + models.push_back("meshes/"+part->mModel); + } + } + } + } + + // preload body parts + if (race) + { + const std::vector& parts = MWRender::NpcAnimation::getBodyParts(Misc::StringUtils::lowerCase(race->mId), female, false, false); + for (std::vector::const_iterator it = parts.begin(); it != parts.end(); ++it) + { + const ESM::BodyPart* part = *it; + if (part && !part->mModel.empty()) + models.push_back("meshes/"+part->mModel); + } + } + + } + std::string Npc::getName (const MWWorld::ConstPtr& ptr) const { if(ptr.getRefData().getCustomData() && ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats.isWerewolf()) @@ -551,18 +639,7 @@ namespace MWClass damage *= store.find("fCombatKODamageMult")->getFloat(); // Apply "On hit" enchanted weapons - std::string enchantmentName = !weapon.isEmpty() ? weapon.getClass().getEnchantment(weapon) : ""; - if (!enchantmentName.empty()) - { - const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( - enchantmentName); - if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) - { - MWMechanics::CastSpell cast(ptr, victim); - cast.mHitPosition = hitPosition; - cast.cast(weapon); - } - } + MWMechanics::applyOnStrikeEnchantment(ptr, victim, weapon, hitPosition); MWMechanics::applyElementalShields(ptr, victim); @@ -1217,20 +1294,31 @@ namespace MWClass void Npc::respawn(const MWWorld::Ptr &ptr) const { - if (ptr.get()->mBase->mFlags & ESM::NPC::Respawn) + const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); + if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead()) + return; + + const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->getFloat(); + static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->getFloat(); + + float delay = ptr.getRefData().getCount() == 0 ? fCorpseClearDelay : std::min(fCorpseRespawnDelay, fCorpseClearDelay); + + if (ptr.get()->mBase->mFlags & ESM::NPC::Respawn + && creatureStats.getTimeOfDeath() + delay <= MWBase::Environment::get().getWorld()->getTimeStamp()) { - // Note we do not respawn moved references in the cell they were moved to. Instead they are respawned in the original cell. - // This also means we cannot respawn dynamically placed references with no content file connection. if (ptr.getCellRef().hasContentFile()) { if (ptr.getRefData().getCount() == 0) ptr.getRefData().setCount(1); - // Reset to original position - ptr.getRefData().setPosition(ptr.getCellRef().getPosition()); - MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); ptr.getRefData().setCustomData(NULL); + + // Reset to original position + MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().pos[0], + ptr.getCellRef().getPosition().pos[1], + ptr.getCellRef().getPosition().pos[2]); } } } diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index 5df34380a5..95edbd4089 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -75,6 +75,9 @@ namespace MWClass virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const; + virtual void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const; + ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: list getModel(). + virtual boost::shared_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const; ///< Generate action for activation diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 40b4d62342..c26b925f6f 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -20,7 +20,7 @@ #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" -#include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/alchemy.hpp" namespace MWClass { @@ -124,17 +124,8 @@ namespace MWClass // hide effects the player doesnt know about MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - MWMechanics::NpcStats& npcStats = player.getClass().getNpcStats (player); - int alchemySkill = npcStats.getSkill (ESM::Skill::Alchemy).getBase(); - int i=0; - static const float fWortChanceValue = - MWBase::Environment::get().getWorld()->getStore().get().find("fWortChanceValue")->getFloat(); - for (MWGui::Widgets::SpellEffectList::iterator it = info.effects.begin(); it != info.effects.end(); ++it) - { - it->mKnown = (i <= 1 && alchemySkill >= fWortChanceValue) - || (i <= 3 && alchemySkill >= fWortChanceValue*2); - ++i; - } + for (unsigned int i=0; igetPersuasionDispositionChange( - mActor, MWBase::MechanicsManager::PersuasionType(type), mTemporaryDispositionChange, + mActor, MWBase::MechanicsManager::PersuasionType(type), success, temp, perm); mTemporaryDispositionChange += temp; mPermanentDispositionChange += perm; // change temp disposition so that final disposition is between 0...100 - float curDisp = static_cast(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor)); + float curDisp = static_cast(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor, false)); if (curDisp + mTemporaryDispositionChange < 0) mTemporaryDispositionChange = -curDisp; else if (curDisp + mTemporaryDispositionChange > 100) @@ -629,7 +629,8 @@ namespace MWDialogue const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Dialogue *dial = store.get().find(topic); - Filter filter(actor, 0, false); + const MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor); + Filter filter(actor, 0, creatureStats.hasTalkedToPlayer()); const ESM::DialInfo *info = filter.search(*dial, false); if(info != NULL) { diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index 43d979ea28..9f31fdda6e 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -62,7 +62,15 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const } // NPC faction - if (!info.mFaction.empty()) + if (info.mFactionLess) + { + if (isCreature) + return true; + + if (!mActor.getClass().getPrimaryFaction(mActor).empty()) + return false; + } + else if (!info.mFaction.empty()) { if (isCreature) return true; @@ -145,8 +153,7 @@ bool MWDialogue::Filter::testDisposition (const ESM::DialInfo& info, bool invert if (isCreature) return true; - int actorDisposition = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor) - + MWBase::Environment::get().getDialogueManager()->getTemporaryDispositionChange(); + int actorDisposition = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor); // For service refusal, the disposition check is inverted. However, a value of 0 still means "always succeed". return invert ? (info.mData.mDisposition == 0 || actorDisposition < info.mData.mDisposition) : (actorDisposition >= info.mData.mDisposition); @@ -155,10 +162,18 @@ bool MWDialogue::Filter::testDisposition (const ESM::DialInfo& info, bool invert bool MWDialogue::Filter::testSelectStruct (const SelectWrapper& select) const { if (select.isNpcOnly() && (mActor.getTypeName() != typeid (ESM::NPC).name())) - // If the actor is a creature, we do not test the conditions applicable - // only to NPCs. + // If the actor is a creature, we pass all conditions only applicable to NPCs. return true; + if (select.getFunction() == SelectWrapper::Function_Choice && mChoice == -1) + // If not currently in a choice, we reject all conditions that test against choices. + return false; + + if (select.getFunction() == SelectWrapper::Function_Weather && !(MWBase::Environment::get().getWorld()->isCellExterior() || MWBase::Environment::get().getWorld()->isCellQuasiExterior())) + // Reject weather conditions in interior cells + // Note that the original engine doesn't include the "|| isCellQuasiExterior()" check, which could be considered a bug. + return false; + switch (select.getType()) { case SelectWrapper::Type_None: return true; @@ -478,7 +493,7 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co case SelectWrapper::Function_SameRace: - return !Misc::StringUtils::ciEqual(mActor.get()->mBase->mRace, player.get()->mBase->mRace); + return Misc::StringUtils::ciEqual(mActor.get()->mBase->mRace, player.get()->mBase->mRace); case SelectWrapper::Function_SameFaction: diff --git a/apps/openmw/mwdialogue/journalentry.cpp b/apps/openmw/mwdialogue/journalentry.cpp index 2f5f02b01f..9f74d0733d 100644 --- a/apps/openmw/mwdialogue/journalentry.cpp +++ b/apps/openmw/mwdialogue/journalentry.cpp @@ -102,8 +102,8 @@ namespace MWDialogue {} StampedJournalEntry::StampedJournalEntry (const std::string& topic, const std::string& infoId, - int day, int month, int dayOfMonth) - : JournalEntry (topic, infoId, MWWorld::Ptr()), mDay (day), mMonth (month), mDayOfMonth (dayOfMonth) + int day, int month, int dayOfMonth, const MWWorld::Ptr& actor) + : JournalEntry (topic, infoId, actor), mDay (day), mMonth (month), mDayOfMonth (dayOfMonth) {} StampedJournalEntry::StampedJournalEntry (const ESM::JournalEntry& record) @@ -119,12 +119,12 @@ namespace MWDialogue entry.mDayOfMonth = mDayOfMonth; } - StampedJournalEntry StampedJournalEntry::makeFromQuest (const std::string& topic, int index) + StampedJournalEntry StampedJournalEntry::makeFromQuest (const std::string& topic, int index, const MWWorld::Ptr& actor) { int day = MWBase::Environment::get().getWorld()->getGlobalInt ("dayspassed"); int month = MWBase::Environment::get().getWorld()->getGlobalInt ("month"); int dayOfMonth = MWBase::Environment::get().getWorld()->getGlobalInt ("day"); - return StampedJournalEntry (topic, idFromIndex (topic, index), day, month, dayOfMonth); + return StampedJournalEntry (topic, idFromIndex (topic, index), day, month, dayOfMonth, actor); } } diff --git a/apps/openmw/mwdialogue/journalentry.hpp b/apps/openmw/mwdialogue/journalentry.hpp index 3ae3efcc8d..8711ab53a7 100644 --- a/apps/openmw/mwdialogue/journalentry.hpp +++ b/apps/openmw/mwdialogue/journalentry.hpp @@ -64,13 +64,13 @@ namespace MWDialogue StampedJournalEntry(); StampedJournalEntry (const std::string& topic, const std::string& infoId, - int day, int month, int dayOfMonth); + int day, int month, int dayOfMonth, const MWWorld::Ptr& actor); StampedJournalEntry (const ESM::JournalEntry& record); void write (ESM::JournalEntry& entry) const; - static StampedJournalEntry makeFromQuest (const std::string& topic, int index); + static StampedJournalEntry makeFromQuest (const std::string& topic, int index, const MWWorld::Ptr& actor); }; } diff --git a/apps/openmw/mwdialogue/journalimp.cpp b/apps/openmw/mwdialogue/journalimp.cpp index e6ffe22ab2..8ea72e3ba3 100644 --- a/apps/openmw/mwdialogue/journalimp.cpp +++ b/apps/openmw/mwdialogue/journalimp.cpp @@ -75,7 +75,7 @@ namespace MWDialogue mTopics.clear(); } - void Journal::addEntry (const std::string& id, int index) + void Journal::addEntry (const std::string& id, int index, const MWWorld::Ptr& actor) { // bail out of we already have heard this... std::string infoId = JournalEntry::idFromIndex (id, index); @@ -83,7 +83,7 @@ namespace MWDialogue if (i->mTopic == id && i->mInfoId == infoId) return; - StampedJournalEntry entry = StampedJournalEntry::makeFromQuest (id, index); + StampedJournalEntry entry = StampedJournalEntry::makeFromQuest (id, index, actor); mJournal.push_back (entry); diff --git a/apps/openmw/mwdialogue/journalimp.hpp b/apps/openmw/mwdialogue/journalimp.hpp index 7f26a5bb90..c3e9406290 100644 --- a/apps/openmw/mwdialogue/journalimp.hpp +++ b/apps/openmw/mwdialogue/journalimp.hpp @@ -29,8 +29,9 @@ namespace MWDialogue virtual void clear(); - virtual void addEntry (const std::string& id, int index); + virtual void addEntry (const std::string& id, int index, const MWWorld::Ptr& actor); ///< Add a journal entry. + /// @param actor Used as context for replacing of escape sequences (%name, etc). virtual void setJournalIndex (const std::string& id, int index); ///< Set the journal index without adding an entry. diff --git a/apps/openmw/mwdialogue/keywordsearch.hpp b/apps/openmw/mwdialogue/keywordsearch.hpp index 3532dc22b8..f296f223fb 100644 --- a/apps/openmw/mwdialogue/keywordsearch.hpp +++ b/apps/openmw/mwdialogue/keywordsearch.hpp @@ -158,7 +158,7 @@ public: } // resolve overlapping keywords - while (matches.size()) + while (!matches.empty()) { int longestKeywordSize = 0; typename std::vector::iterator longestKeyword = matches.begin(); diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index d12c22e06d..61a0efc460 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -67,46 +67,29 @@ namespace MWGui { MWMechanics::Alchemy::Result result = mAlchemy->create (mNameEdit->getCaption ()); - if (result == MWMechanics::Alchemy::Result_NoName) + switch (result) { + case MWMechanics::Alchemy::Result_NoName: MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage37}"); - return; - } - - // check if mortar & pestle is available (always needed) - if (result == MWMechanics::Alchemy::Result_NoMortarAndPestle) - { + break; + case MWMechanics::Alchemy::Result_NoMortarAndPestle: MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage45}"); - return; - } - - // make sure 2 or more ingredients were selected - if (result == MWMechanics::Alchemy::Result_LessThanTwoIngredients) - { + break; + case MWMechanics::Alchemy::Result_LessThanTwoIngredients: MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage6a}"); - return; - } - - if (result == MWMechanics::Alchemy::Result_NoEffects) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage8}"); - MWBase::Environment::get().getSoundManager()->playSound("potion fail", 1.f, 1.f); - return; - } - - if (result == MWMechanics::Alchemy::Result_Success) - { + break; + case MWMechanics::Alchemy::Result_Success: MWBase::Environment::get().getWindowManager()->messageBox("#{sPotionSuccess}"); MWBase::Environment::get().getSoundManager()->playSound("potion success", 1.f, 1.f); - } - else if (result == MWMechanics::Alchemy::Result_RandomFailure) - { - // potion failed + break; + case MWMechanics::Alchemy::Result_NoEffects: + case MWMechanics::Alchemy::Result_RandomFailure: MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage8}"); MWBase::Environment::get().getSoundManager()->playSound("potion fail", 1.f, 1.f); + break; } - // reduce count of the ingredients + // remove ingredient slots that have been fully used up for (int i=0; i<4; ++i) if (mIngredients[i]->isUserString("ToolTipType")) { @@ -216,6 +199,7 @@ namespace MWGui std::set effectIds = mAlchemy->listEffects(); Widgets::SpellEffectList list; + unsigned int effectIndex=0; for (std::set::iterator it = effectIds.begin(); it != effectIds.end(); ++it) { Widgets::SpellEffectParams params; @@ -228,7 +212,10 @@ namespace MWGui params.mIsConstant = true; params.mNoTarget = true; + params.mKnown = mAlchemy->knownEffect(effectIndex, MWBase::Environment::get().getWorld()->getPlayerPtr()); + list.push_back(params); + ++effectIndex; } while (mEffectsBox->getChildCount()) diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index b728b748f7..dbd829176f 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -1153,8 +1153,6 @@ public: void createDrawItem(MyGUI::ITexture* texture, MyGUI::ILayerNode* node) { - //test (); - mNode = node; for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) @@ -1222,6 +1220,11 @@ public: void _updateView () { + _checkMargin(); + + if (mNode != NULL) + for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) + mNode->outOfDate (i->second->mRenderItem); } void _correctView() @@ -1249,72 +1252,65 @@ class BookPageImpl : public BookPage MYGUI_RTTI_DERIVED(BookPage) public: + BookPageImpl() + : mPageDisplay(NULL) + { + } + void showPage (TypesetBook::Ptr book, size_t page) { - if (PageDisplay* pd = dynamic_cast (getSubWidgetText ())) - pd->showPage (book, page); - else - throw std::runtime_error ("The main sub-widget for a BookPage must be a PageDisplay."); + mPageDisplay->showPage (book, page); } void adviseLinkClicked (boost::function linkClicked) { - if (PageDisplay* pd = dynamic_cast (getSubWidgetText ())) - { - pd->mLinkClicked = linkClicked; - } + mPageDisplay->mLinkClicked = linkClicked; } void unadviseLinkClicked () { - if (PageDisplay* pd = dynamic_cast (getSubWidgetText ())) - { - pd->mLinkClicked = boost::function (); - } + mPageDisplay->mLinkClicked = boost::function (); } protected: + + virtual void initialiseOverride() + { + Base::initialiseOverride(); + + if (getSubWidgetText()) + { + mPageDisplay = getSubWidgetText()->castType(); + } + else + { + throw std::runtime_error("BookPage unable to find page display sub widget"); + } + } + void onMouseLostFocus(Widget* _new) { // NOTE: MyGUI also fires eventMouseLostFocus for widgets that are about to be destroyed (if they had focus). // Child widgets may already be destroyed! So be careful. - if (PageDisplay* pd = dynamic_cast (getSubWidgetText ())) - { - pd->onMouseLostFocus (); - } - else - Widget::onMouseLostFocus (_new); + mPageDisplay->onMouseLostFocus (); } void onMouseMove(int left, int top) { - if (PageDisplay* pd = dynamic_cast (getSubWidgetText ())) - { - pd->onMouseMove (left, top); - } - else - Widget::onMouseMove (left, top); + mPageDisplay->onMouseMove (left, top); } void onMouseButtonPressed (int left, int top, MyGUI::MouseButton id) { - if (PageDisplay* pd = dynamic_cast (getSubWidgetText ())) - { - pd->onMouseButtonPressed (left, top, id); - } - else - Widget::onMouseButtonPressed (left, top, id); + mPageDisplay->onMouseButtonPressed (left, top, id); } void onMouseButtonReleased(int left, int top, MyGUI::MouseButton id) { - if (PageDisplay* pd = dynamic_cast (getSubWidgetText ())) - { - pd->onMouseButtonReleased (left, top, id); - } - else - Widget::onMouseButtonReleased (left, top, id); + mPageDisplay->onMouseButtonReleased (left, top, id); } + + PageDisplay* mPageDisplay; }; void BookPage::registerMyGUIComponents () diff --git a/apps/openmw/mwgui/charactercreation.cpp b/apps/openmw/mwgui/charactercreation.cpp index d2ce35509d..a880bca45d 100644 --- a/apps/openmw/mwgui/charactercreation.cpp +++ b/apps/openmw/mwgui/charactercreation.cpp @@ -1,5 +1,7 @@ #include "charactercreation.hpp" +#include + #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -10,7 +12,6 @@ #include "../mwmechanics/actorutil.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/fallback.hpp" #include "../mwworld/esmstore.hpp" #include "textinput.hpp" @@ -32,7 +33,7 @@ namespace const ESM::Class::Specialization mSpecializations[3]={ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth}; // The specialization for each answer Step sGenerateClassSteps(int number) { number++; - const MWWorld::Fallback* fallback=MWBase::Environment::get().getWorld()->getFallback(); + const Fallback::Map* fallback=MWBase::Environment::get().getWorld()->getFallback(); Step step = {fallback->getFallbackString("Question_"+MyGUI::utility::toString(number)+"_Question"), {fallback->getFallbackString("Question_"+MyGUI::utility::toString(number)+"_AnswerOne"), fallback->getFallbackString("Question_"+MyGUI::utility::toString(number)+"_AnswerTwo"), @@ -176,7 +177,7 @@ namespace MWGui mPickClassDialog = 0; mPickClassDialog = new PickClassDialog(); mPickClassDialog->setNextButtonShow(mCreationStage >= CSE_ClassChosen); - mPickClassDialog->setClassId(mPlayerClass.mName); + mPickClassDialog->setClassId(mPlayerClass.mId); mPickClassDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onPickClassDialogDone); mPickClassDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onPickClassDialogBack); mPickClassDialog->setVisible(true); diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp index 57cd9ca8eb..ab7290e875 100644 --- a/apps/openmw/mwgui/class.cpp +++ b/apps/openmw/mwgui/class.cpp @@ -55,7 +55,9 @@ namespace MWGui void GenerateClassResultDialog::setClassId(const std::string &classId) { mCurrentClassId = classId; - mClassImage->setImageTexture(std::string("textures\\levelup\\") + mCurrentClassId + ".dds"); + + setClassImage(mClassImage, mCurrentClassId); + mClassName->setCaption(MWBase::Environment::get().getWorld()->getStore().get().find(mCurrentClassId)->mName); center(); @@ -199,6 +201,9 @@ namespace MWGui if (!playable) // Only display playable classes continue; + if (store.get().isDynamic(it->mId)) + continue; // custom-made class not relevant for this dialog + items.push_back(std::make_pair(it->mId, it->mName)); } std::sort(items.begin(), items.end(), sortClasses); @@ -254,7 +259,7 @@ namespace MWGui ToolTips::createSkillToolTip(mMajorSkill[i], klass->mData.mSkills[i][1]); } - mClassImage->setImageTexture(std::string("textures\\levelup\\") + mCurrentClassId + ".dds"); + setClassImage(mClassImage, mCurrentClassId); } /* InfoBoxDialog */ @@ -900,4 +905,15 @@ namespace MWGui eventDone(this); } + void setClassImage(MyGUI::ImageBox* imageBox, const std::string &classId) + { + std::string classImage = std::string("textures\\levelup\\") + classId + ".dds"; + if (!MWBase::Environment::get().getWindowManager()->textureExists(classImage)) + { + std::cout << "No class image for " << classId << ", falling back to default" << std::endl; + classImage = "textures\\levelup\\warrior.dds"; + } + imageBox->setImageTexture(classImage); + } + } diff --git a/apps/openmw/mwgui/class.hpp b/apps/openmw/mwgui/class.hpp index e36a9a98b6..8206d6b037 100644 --- a/apps/openmw/mwgui/class.hpp +++ b/apps/openmw/mwgui/class.hpp @@ -8,6 +8,8 @@ namespace MWGui { + void setClassImage(MyGUI::ImageBox* imageBox, const std::string& classId); + class InfoBoxDialog : public WindowModal { public: diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index 1777d86ad4..aeff7dc39a 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -240,7 +240,7 @@ namespace MWGui mCommandLine->setCaption(newCaption); // List candidates if repeatedly pressing tab - if (oldCaption == newCaption && matches.size()) + if (oldCaption == newCaption && !matches.empty()) { int i = 0; printOK(""); diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 5d71fc445d..00df18e1db 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -57,7 +57,7 @@ namespace MWGui { if (mDragAndDrop->mIsOnDragAndDrop) { - if (!dynamic_cast(mModel)) + if (mModel && mModel->allowedToInsertItems()) dropItem(); return; } @@ -126,7 +126,7 @@ namespace MWGui void ContainerWindow::onBackgroundSelected() { - if (mDragAndDrop->mIsOnDragAndDrop && !dynamic_cast(mModel)) + if (mDragAndDrop->mIsOnDragAndDrop && mModel && mModel->allowedToInsertItems()) dropItem(); } @@ -271,7 +271,7 @@ namespace MWGui { int value = item.mBase.getClass().getValue(item.mBase) * count; MWBase::Environment::get().getMechanicsManager()->commitCrime( - player, MWWorld::Ptr(), MWBase::MechanicsManager::OT_Theft, value, true); + player, mPtr, MWBase::MechanicsManager::OT_Theft, value, true); MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Container); mPickpocketDetected = true; return false; diff --git a/apps/openmw/mwgui/containeritemmodel.cpp b/apps/openmw/mwgui/containeritemmodel.cpp index b2befc3ba8..1e4749695f 100644 --- a/apps/openmw/mwgui/containeritemmodel.cpp +++ b/apps/openmw/mwgui/containeritemmodel.cpp @@ -37,7 +37,7 @@ ContainerItemModel::ContainerItemModel(const std::vector& itemSour : mItemSources(itemSources) , mWorldItems(worldItems) { - assert (mItemSources.size()); + assert (!mItemSources.empty()); } ContainerItemModel::ContainerItemModel (const MWWorld::Ptr& source) diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index d325886316..771e1bf95e 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -547,7 +547,7 @@ namespace MWGui void DialogueWindow::onScrollbarMoved(MyGUI::ScrollBar *sender, size_t pos) { - mHistory->setPosition(0, pos * -1); + mHistory->setPosition(0, static_cast(pos) * -1); } void DialogueWindow::addResponse(const std::string &text, const std::string &title) @@ -639,9 +639,7 @@ namespace MWGui { if(mMainWidget->getVisible() && mEnabled && mPtr.getTypeName() == typeid(ESM::NPC).name()) { - int disp = std::max(0, std::min(100, - MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr) - + MWBase::Environment::get().getDialogueManager()->getTemporaryDispositionChange())); + int disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr); mDispositionBar->setProgressRange(100); mDispositionBar->setProgressPosition(disp); mDispositionText->setCaption(MyGUI::utility::toString(disp)+std::string("/100")); diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index cf0fa3414a..b5c8dc8bb6 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -69,7 +69,7 @@ namespace MWGui HUD::HUD(CustomMarkerCollection &customMarkers, DragAndDrop* dragAndDrop, MWRender::LocalMap* localMapRender) : Layout("openmw_hud.layout") - , LocalMapBase(customMarkers, localMapRender) + , LocalMapBase(customMarkers, localMapRender, Settings::Manager::getBool("local map hud fog of war", "Map")) , mHealth(NULL) , mMagicka(NULL) , mStamina(NULL) @@ -159,7 +159,7 @@ namespace MWGui getWidget(mCrosshair, "Crosshair"); - LocalMapBase::init(mMinimap, mCompass, Settings::Manager::getInt("local map hud widget size", "Map")); + LocalMapBase::init(mMinimap, mCompass, Settings::Manager::getInt("local map hud widget size", "Map"), Settings::Manager::getInt("local map cell distance", "Map")); mMainWidget->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onWorldClicked); mMainWidget->eventMouseMove += MyGUI::newDelegate(this, &HUD::onWorldMouseOver); diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 7678cb006c..4599132e3d 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -87,7 +87,7 @@ namespace MWGui mAvatarImage->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onAvatarClicked); mAvatarImage->setRenderItemTexture(mPreviewTexture.get()); - mAvatarImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); + mAvatarImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); getWidget(mItemView, "ItemView"); mItemView->eventItemClicked += MyGUI::newDelegate(this, &InventoryWindow::onItemSelected); @@ -265,18 +265,20 @@ namespace MWGui } } - void InventoryWindow::ensureSelectedItemUnequipped() + void InventoryWindow::ensureSelectedItemUnequipped(int count) { const ItemStack& item = mTradeModel->getItem(mSelectedItem); if (item.mType == ItemStack::Type_Equipped) { MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr); - MWWorld::Ptr newStack = *invStore.unequipItem(item.mBase, mPtr); + MWWorld::Ptr newStack = *invStore.unequipItemQuantity(item.mBase, mPtr, count); // The unequipped item was re-stacked. We have to update the index // since the item pointed does not exist anymore. if (item.mBase != newStack) { + updateItemView(); // Unequipping can produce a new stack, not yet in the window... + // newIndex will store the index of the ItemStack the item was stacked on int newIndex = -1; for (size_t i=0; i < mTradeModel->getItemCount(); ++i) @@ -298,14 +300,14 @@ namespace MWGui void InventoryWindow::dragItem(MyGUI::Widget* sender, int count) { - ensureSelectedItemUnequipped(); + ensureSelectedItemUnequipped(count); mDragAndDrop->startDrag(mSelectedItem, mSortModel, mTradeModel, mItemView, count); notifyContentChanged(); } void InventoryWindow::sellItem(MyGUI::Widget* sender, int count) { - ensureSelectedItemUnequipped(); + ensureSelectedItemUnequipped(count); const ItemStack& item = mTradeModel->getItem(mSelectedItem); std::string sound = item.mBase.getClass().getDownSoundId(item.mBase); MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); @@ -401,8 +403,8 @@ namespace MWGui int height = std::min(mPreview->getTextureHeight(), size.height); mPreview->setViewport(width, height); - mAvatarImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, height/float(mPreview->getTextureHeight()), - width/float(mPreview->getTextureWidth()), 0.f)); + mAvatarImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, + width/float(mPreview->getTextureWidth()), height/float(mPreview->getTextureHeight()))); } void InventoryWindow::onFilterChanged(MyGUI::Widget* _sender) diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp index a8a1b15a13..b7ae067ace 100644 --- a/apps/openmw/mwgui/inventorywindow.hpp +++ b/apps/openmw/mwgui/inventorywindow.hpp @@ -126,8 +126,8 @@ namespace MWGui void adjustPanes(); - /// Unequips mSelectedItem, if it is equipped, and then updates mSelectedItem in case it was re-stacked - void ensureSelectedItemUnequipped(); + /// Unequips count items from mSelectedItem, if it is equipped, and then updates mSelectedItem in case the items were re-stacked + void ensureSelectedItemUnequipped(int count); }; } diff --git a/apps/openmw/mwgui/itemmodel.cpp b/apps/openmw/mwgui/itemmodel.cpp index 2c382f3cf4..390bb0586a 100644 --- a/apps/openmw/mwgui/itemmodel.cpp +++ b/apps/openmw/mwgui/itemmodel.cpp @@ -119,6 +119,11 @@ namespace MWGui return ret; } + bool ItemModel::allowedToInsertItems() const + { + return true; + } + ProxyItemModel::ProxyItemModel() : mSourceModel(NULL) diff --git a/apps/openmw/mwgui/itemmodel.hpp b/apps/openmw/mwgui/itemmodel.hpp index 2019c10426..20955b2065 100644 --- a/apps/openmw/mwgui/itemmodel.hpp +++ b/apps/openmw/mwgui/itemmodel.hpp @@ -70,6 +70,9 @@ namespace MWGui virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool setNewOwner=false) = 0; virtual void removeItem (const ItemStack& item, size_t count) = 0; + /// Is the player allowed to insert items into this model? (default true) + virtual bool allowedToInsertItems() const; + private: ItemModel(const ItemModel&); ItemModel& operator=(const ItemModel&); diff --git a/apps/openmw/mwgui/jailscreen.cpp b/apps/openmw/mwgui/jailscreen.cpp index 7eaf5eb303..211e0c5f91 100644 --- a/apps/openmw/mwgui/jailscreen.cpp +++ b/apps/openmw/mwgui/jailscreen.cpp @@ -79,9 +79,9 @@ namespace MWGui MWWorld::Ptr player = MWMechanics::getPlayer(); - MWBase::Environment::get().getWorld()->advanceTime(mDays * 24); for (int i=0; irest(true); + MWBase::Environment::get().getWorld()->advanceTime(mDays * 24); std::set skills; for (int day=0; day #include +#include + #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/fallback.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" @@ -18,6 +19,8 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" +#include "class.hpp" + namespace MWGui { const unsigned int LevelupDialog::sMaxCoins = 3; @@ -153,7 +156,9 @@ namespace MWGui cls = &*it; } - mClassImage->setImageTexture ("textures\\levelup\\" + cls->mId + ".dds"); + setClassImage(mClassImage, getLevelupClassImage(pcStats.getSkillIncreasesForSpecialization(0), + pcStats.getSkillIncreasesForSpecialization(1), + pcStats.getSkillIncreasesForSpecialization(2))); int level = creatureStats.getLevel ()+1; mLevelText->setCaptionWithReplacing("#{sLevelUpMenu1} " + MyGUI::utility::toString(level)); @@ -245,4 +250,103 @@ namespace MWGui } assignCoins(); } + + std::string LevelupDialog::getLevelupClassImage(const int combatIncreases, const int magicIncreases, const int stealthIncreases) + { + std::string ret = "acrobat"; + + int total = combatIncreases + magicIncreases + stealthIncreases; + if (total == 0) + return ret; + + int combatFraction = static_cast(static_cast(combatIncreases) / total * 10.f); + int magicFraction = static_cast(static_cast(magicIncreases) / total * 10.f); + int stealthFraction = static_cast(static_cast(stealthIncreases) / total * 10.f); + + if (combatFraction > 7) + ret = "warrior"; + else if (magicFraction > 7) + ret = "mage"; + else if (stealthFraction > 7) + ret = "thief"; + + switch (combatFraction) + { + case 7: + ret = "warrior"; + break; + case 6: + if (stealthFraction == 1) + ret = "barbarian"; + else if (stealthFraction == 3) + ret = "crusader"; + else + ret = "knight"; + break; + case 5: + if (stealthFraction == 3) + ret = "scout"; + else + ret = "archer"; + break; + case 4: + ret = "rogue"; + break; + default: + break; + } + + switch (magicFraction) + { + case 7: + ret = "mage"; + break; + case 6: + if (combatFraction == 2) + ret = "sorcerer"; + else if (combatIncreases == 3) + ret = "healer"; + else + ret = "battlemage"; + break; + case 5: + ret = "witchhunter"; + break; + case 4: + ret = "spellsword"; + // In vanilla there's also code for "nightblade", however it seems to be unreachable. + break; + default: + break; + } + + switch (stealthFraction) + { + case 7: + ret = "thief"; + break; + case 6: + if (magicFraction == 1) + ret = "agent"; + else if (magicIncreases == 3) + ret = "assassin"; + else + ret = "acrobat"; + break; + case 5: + if (magicIncreases == 3) + ret = "monk"; + else + ret = "pilgrim"; + break; + case 3: + if (magicFraction == 3) + ret = "bard"; + break; + default: + break; + } + + return ret; + } } diff --git a/apps/openmw/mwgui/levelupdialog.hpp b/apps/openmw/mwgui/levelupdialog.hpp index e5edbd53c8..d49f7536df 100644 --- a/apps/openmw/mwgui/levelupdialog.hpp +++ b/apps/openmw/mwgui/levelupdialog.hpp @@ -39,6 +39,8 @@ namespace MWGui void resetCoins(); void setAttributeValues(); + + std::string getLevelupClassImage(const int combatIncreases, const int magicIncreases, const int stealthIncreases); }; } diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index e32140cb35..6385eb2c6e 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -164,7 +164,7 @@ namespace MWGui mBackgroundImage->setBackgroundImage(""); mBackgroundImage->setRenderItemTexture(mGuiTexture.get()); - mBackgroundImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); + mBackgroundImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); } setVisible(true); diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 0ebb595ddf..7c52f09cbc 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -156,7 +156,7 @@ namespace MWGui // ------------------------------------------------------ - LocalMapBase::LocalMapBase(CustomMarkerCollection &markers, MWRender::LocalMap* localMapRender) + LocalMapBase::LocalMapBase(CustomMarkerCollection &markers, MWRender::LocalMap* localMapRender, bool fogOfWarEnabled) : mLocalMapRender(localMapRender) , mCurX(0) , mCurY(0) @@ -165,8 +165,11 @@ namespace MWGui , mCompass(NULL) , mPrefix() , mChanged(true) - , mFogOfWar(true) + , mFogOfWarToggled(true) + , mFogOfWarEnabled(fogOfWarEnabled) , mMapWidgetSize(0) + , mNumCells(0) + , mCellDistance(0) , mCustomMarkers(markers) , mMarkerUpdateTimer(0.0f) , mLastDirectionX(0.0f) @@ -180,20 +183,22 @@ namespace MWGui mCustomMarkers.eventMarkersChanged -= MyGUI::newDelegate(this, &LocalMapBase::updateCustomMarkers); } - void LocalMapBase::init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass, int mapWidgetSize) + void LocalMapBase::init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass, int mapWidgetSize, int cellDistance) { mLocalMap = widget; mCompass = compass; mMapWidgetSize = mapWidgetSize; + mCellDistance = cellDistance; + mNumCells = cellDistance * 2 + 1; - mLocalMap->setCanvasSize(mMapWidgetSize*3, mMapWidgetSize*3); + mLocalMap->setCanvasSize(mMapWidgetSize*mNumCells, mMapWidgetSize*mNumCells); mCompass->setDepth(Local_CompassLayer); mCompass->setNeedMouseFocus(false); - for (int mx=0; mx<3; ++mx) + for (int mx=0; mxcreateWidget("ImageBox", MyGUI::IntCoord(mx*mMapWidgetSize, my*mMapWidgetSize, mMapWidgetSize, mMapWidgetSize), @@ -222,23 +227,23 @@ namespace MWGui bool LocalMapBase::toggleFogOfWar() { - mFogOfWar = !mFogOfWar; + mFogOfWarToggled = !mFogOfWarToggled; applyFogOfWar(); - return mFogOfWar; + return mFogOfWarToggled; } void LocalMapBase::applyFogOfWar() { TextureVector fogTextures; - for (int mx=0; mx<3; ++mx) + for (int mx=0; mxsetImageTexture(""); continue; @@ -249,7 +254,7 @@ namespace MWGui { boost::shared_ptr myguitex (new osgMyGUI::OSGTexture(tex)); fog->setRenderItemTexture(myguitex.get()); - fog->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); + fog->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); fogTextures.push_back(myguitex); } else @@ -269,8 +274,6 @@ namespace MWGui // normalized cell coordinates float nX,nY; - markerPos.interior = mInterior; - if (!mInterior) { int cellX, cellY; @@ -285,8 +288,8 @@ namespace MWGui markerPos.cellX = cellX; markerPos.cellY = cellY; - widgetPos = MyGUI::IntPoint(static_cast(nX * mMapWidgetSize + (1 + cellDx) * mMapWidgetSize), - static_cast(nY * mMapWidgetSize - (cellDy-1) * mMapWidgetSize)); + widgetPos = MyGUI::IntPoint(static_cast(nX * mMapWidgetSize + (mCellDistance + cellDx) * mMapWidgetSize), + static_cast(nY * mMapWidgetSize + (mCellDistance - cellDy) * mMapWidgetSize)); } else { @@ -298,8 +301,8 @@ namespace MWGui markerPos.cellY = cellY; // Image space is -Y up, cells are Y up - widgetPos = MyGUI::IntPoint(static_cast(nX * mMapWidgetSize + (1 + (cellX - mCurX)) * mMapWidgetSize), - static_cast(nY * mMapWidgetSize + (1-(cellY-mCurY)) * mMapWidgetSize)); + widgetPos = MyGUI::IntPoint(static_cast(nX * mMapWidgetSize + (mCellDistance + (cellX - mCurX)) * mMapWidgetSize), + static_cast(nY * mMapWidgetSize + (mCellDistance - (cellY - mCurY)) * mMapWidgetSize)); } markerPos.nX = nX; @@ -313,13 +316,13 @@ namespace MWGui MyGUI::Gui::getInstance().destroyWidget(*it); mCustomMarkerWidgets.clear(); - for (int dX = -1; dX <= 1; ++dX) + for (int dX = -mCellDistance; dX <= mCellDistance; ++dX) { - for (int dY =-1; dY <= 1; ++dY) + for (int dY =-mCellDistance; dY <= mCellDistance; ++dY) { ESM::CellId cellId; cellId.mPaged = !mInterior; - cellId.mWorldspace = (mInterior ? mPrefix : "sys::default"); + cellId.mWorldspace = (mInterior ? mPrefix : ESM::CellId::sDefaultWorldspace); cellId.mIndex.mX = mCurX+dX; cellId.mIndex.mY = mCurY+dY; @@ -373,14 +376,14 @@ namespace MWGui // Update the map textures TextureVector textures; - for (int mx=0; mx<3; ++mx) + for (int mx=0; mx texture = mLocalMapRender->getMapTexture(mapX, mapY); if (texture) @@ -388,7 +391,7 @@ namespace MWGui boost::shared_ptr guiTex (new osgMyGUI::OSGTexture(texture)); textures.push_back(guiTex); box->setRenderItemTexture(guiTex.get()); - box->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); + box->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); } else box->setRenderItemTexture(NULL); @@ -407,9 +410,9 @@ namespace MWGui } else { - for (int dX=-1; dX<2; ++dX) + for (int dX=-mCellDistance; dX<=mCellDistance; ++dX) { - for (int dY=-1; dY<2; ++dY) + for (int dY=-mCellDistance; dY<=mCellDistance; ++dY) { MWWorld::CellStore* cell = world->getExterior (mCurX+dX, mCurY+dY); world->getDoorMarkers(cell, doors); @@ -455,6 +458,26 @@ namespace MWGui updateCustomMarkers(); } + void LocalMapBase::requestMapRender(const MWWorld::CellStore *cell) + { + std::set cells; + if (!cell->isExterior()) + cells.insert(cell); + else + { + for (int dX=-mCellDistance; dX<=mCellDistance; ++dX) + { + for (int dY=-mCellDistance; dY<=mCellDistance; ++dY) + { + const MWWorld::CellStore* gridCell = MWBase::Environment::get().getWorld()->getExterior (cell->getCell()->getGridX()+dX, cell->getCell()->getGridY()+dY); + cells.insert(gridCell); + } + } + } + + mLocalMapRender->requestMap(cells); + } + void LocalMapBase::redraw() { // Redraw children in proper order @@ -463,7 +486,7 @@ namespace MWGui void LocalMapBase::setPlayerPos(int cellX, int cellY, const float nx, const float ny) { - MyGUI::IntPoint pos(static_cast(mMapWidgetSize + nx*mMapWidgetSize - 16), static_cast(mMapWidgetSize + ny*mMapWidgetSize - 16)); + MyGUI::IntPoint pos(static_cast(mMapWidgetSize * mCellDistance + nx*mMapWidgetSize - 16), static_cast(mMapWidgetSize * mCellDistance + ny*mMapWidgetSize - 16)); pos.left += (cellX - mCurX) * mMapWidgetSize; pos.top -= (cellY - mCurY) * mMapWidgetSize; @@ -644,7 +667,8 @@ namespace MWGui mEventBoxLocal->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); mEventBoxLocal->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &MapWindow::onMapDoubleClicked); - LocalMapBase::init(mLocalMap, mPlayerArrowLocal, Settings::Manager::getInt("local map widget size", "Map")); } + LocalMapBase::init(mLocalMap, mPlayerArrowLocal, Settings::Manager::getInt("local map widget size", "Map"), Settings::Manager::getInt("local map cell distance", "Map")); + } void MapWindow::onNoteEditOk() { @@ -688,8 +712,8 @@ namespace MWGui MyGUI::IntPoint clickedPos = MyGUI::InputManager::getInstance().getMousePosition(); MyGUI::IntPoint widgetPos = clickedPos - mEventBoxLocal->getAbsolutePosition(); - int x = int(widgetPos.left/float(mMapWidgetSize))-1; - int y = (int(widgetPos.top/float(mMapWidgetSize))-1)*-1; + int x = int(widgetPos.left/float(mMapWidgetSize))-mCellDistance; + int y = (int(widgetPos.top/float(mMapWidgetSize))-mCellDistance)*-1; float nX = widgetPos.left/float(mMapWidgetSize) - int(widgetPos.left/float(mMapWidgetSize)); float nY = widgetPos.top/float(mMapWidgetSize) - int(widgetPos.top/float(mMapWidgetSize)); x += mCurX; @@ -714,7 +738,7 @@ namespace MWGui mEditingMarker.mCell.mWorldspace = LocalMapBase::mPrefix; else { - mEditingMarker.mCell.mWorldspace = "sys::default"; + mEditingMarker.mCell.mWorldspace = ESM::CellId::sDefaultWorldspace; mEditingMarker.mCell.mIndex.mX = x; mEditingMarker.mCell.mIndex.mY = y; } @@ -746,11 +770,11 @@ namespace MWGui mGlobalMapTexture.reset(new osgMyGUI::OSGTexture(mGlobalMapRender->getBaseTexture())); mGlobalMapImage->setRenderItemTexture(mGlobalMapTexture.get()); - mGlobalMapImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); + mGlobalMapImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); mGlobalMapOverlayTexture.reset(new osgMyGUI::OSGTexture(mGlobalMapRender->getOverlayTexture())); mGlobalMapOverlay->setRenderItemTexture(mGlobalMapOverlayTexture.get()); - mGlobalMapOverlay->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); + mGlobalMapOverlay->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); } MapWindow::~MapWindow() @@ -824,7 +848,7 @@ namespace MWGui ESM::CellId cellId; cellId.mIndex.mX = x; cellId.mIndex.mY = y; - cellId.mWorldspace = "sys::default"; + cellId.mWorldspace = ESM::CellId::sDefaultWorldspace; cellId.mPaged = true; CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(cellId); std::vector destNotes; @@ -1078,7 +1102,7 @@ namespace MWGui { if (!mLocalMapRender) return true; - return mLocalMapRender->isPositionExplored(nX, nY, cellX, cellY, interior); + return mLocalMapRender->isPositionExplored(nX, nY, cellX, cellY); } } diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index a41d5d5270..773522903a 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -23,6 +23,11 @@ namespace ESM class ESMWriter; } +namespace MWWorld +{ + class CellStore; +} + namespace Loading { class Listener; @@ -61,12 +66,13 @@ namespace MWGui class LocalMapBase { public: - LocalMapBase(CustomMarkerCollection& markers, MWRender::LocalMap* localMapRender); + LocalMapBase(CustomMarkerCollection& markers, MWRender::LocalMap* localMapRender, bool fogOfWarEnabled = true); virtual ~LocalMapBase(); - void init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass, int mapWidgetSize); + void init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass, int mapWidgetSize, int cellDistance); void setCellPrefix(const std::string& prefix); void setActiveCell(const int x, const int y, bool interior=false); + void requestMapRender(const MWWorld::CellStore* cell); void setPlayerDir(const float x, const float y); void setPlayerPos(int cellX, int cellY, const float nx, const float ny); @@ -78,7 +84,6 @@ namespace MWGui { MarkerUserData(MWRender::LocalMap* map) : mLocalMapRender(map) - , interior(false) , cellX(0) , cellY(0) , nX(0.f) @@ -89,7 +94,6 @@ namespace MWGui bool isPositionExplored() const; MWRender::LocalMap* mLocalMapRender; - bool interior; int cellX; int cellY; float nX; @@ -107,10 +111,14 @@ namespace MWGui MyGUI::ImageBox* mCompass; std::string mPrefix; bool mChanged; - bool mFogOfWar; + bool mFogOfWarToggled; + bool mFogOfWarEnabled; int mMapWidgetSize; + int mNumCells; // for convenience, mCellDistance * 2 + 1 + int mCellDistance; + // Stores markers that were placed by a player. May be shared between multiple map views. CustomMarkerCollection& mCustomMarkers; diff --git a/apps/openmw/mwgui/pickpocketitemmodel.cpp b/apps/openmw/mwgui/pickpocketitemmodel.cpp index ab0d02f950..238fb59131 100644 --- a/apps/openmw/mwgui/pickpocketitemmodel.cpp +++ b/apps/openmw/mwgui/pickpocketitemmodel.cpp @@ -64,4 +64,10 @@ namespace MWGui /// \todo check if player is detected } + bool PickpocketItemModel::allowedToInsertItems() const + { + // don't allow "reverse pickpocket" (yet) + return false; + } + } diff --git a/apps/openmw/mwgui/pickpocketitemmodel.hpp b/apps/openmw/mwgui/pickpocketitemmodel.hpp index af72119c89..61f0569b5e 100644 --- a/apps/openmw/mwgui/pickpocketitemmodel.hpp +++ b/apps/openmw/mwgui/pickpocketitemmodel.hpp @@ -15,6 +15,7 @@ namespace MWGui virtual size_t getItemCount(); virtual void update(); virtual void removeItem (const ItemStack& item, size_t count); + virtual bool allowedToInsertItems() const; private: std::vector mHiddenItems; diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp index a65379fca4..6c08f7dc1a 100644 --- a/apps/openmw/mwgui/race.cpp +++ b/apps/openmw/mwgui/race.cpp @@ -142,7 +142,7 @@ namespace MWGui mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture())); mPreviewImage->setRenderItemTexture(mPreviewTexture.get()); - mPreviewImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); + mPreviewImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); const ESM::NPC& proto = mPreview->getPrototype(); setRaceId(proto.mRace); @@ -326,8 +326,11 @@ namespace MWGui record.mRace = mCurrentRaceId; record.setIsMale(mGenderIndex == 0); - record.mHead = mAvailableHeads[mFaceIndex]; - record.mHair = mAvailableHairs[mHairIndex]; + if (mFaceIndex >= 0 && mFaceIndex < int(mAvailableHeads.size())) + record.mHead = mAvailableHeads[mFaceIndex]; + + if (mHairIndex >= 0 && mHairIndex < int(mAvailableHairs.size())) + record.mHair = mAvailableHairs[mHairIndex]; try { diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index 53c2807374..c4d9bae30a 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -86,16 +86,20 @@ namespace MWGui { MWBase::Environment::get().getStateManager()->deleteGame (mCurrentCharacter, mCurrentSlot); mSaveList->removeItemAt(mSaveList->getIndexSelected()); - onSlotSelected(mSaveList, MyGUI::ITEM_NONE); + onSlotSelected(mSaveList, mSaveList->getIndexSelected()); + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveList); - // The character might be deleted now - size_t previousIndex = mCharacterSelection->getIndexSelected(); - open(); - if (mCharacterSelection->getItemCount()) + if (mSaveList->getItemCount() == 0) { - size_t nextCharacter = std::min(previousIndex, mCharacterSelection->getItemCount()-1); - mCharacterSelection->setIndexSelected(nextCharacter); - onCharacterSelected(mCharacterSelection, nextCharacter); + // The character might be deleted now + size_t previousIndex = mCharacterSelection->getIndexSelected(); + open(); + if (mCharacterSelection->getItemCount()) + { + size_t nextCharacter = std::min(previousIndex, mCharacterSelection->getItemCount()-1); + mCharacterSelection->setIndexSelected(nextCharacter); + onCharacterSelected(mCharacterSelection, nextCharacter); + } } } @@ -132,7 +136,7 @@ namespace MWGui if (mgr->characterBegin() == mgr->characterEnd()) return; - mCurrentCharacter = mgr->getCurrentCharacter (false); + mCurrentCharacter = mgr->getCurrentCharacter(); std::string directory = Misc::StringUtils::lowerCase (Settings::Manager::getString ("character", "Saves")); @@ -202,7 +206,7 @@ namespace MWGui if (!load) { - mCurrentCharacter = MWBase::Environment::get().getStateManager()->getCurrentCharacter (false); + mCurrentCharacter = MWBase::Environment::get().getStateManager()->getCurrentCharacter(); } center(); @@ -422,6 +426,6 @@ namespace MWGui mScreenshotTexture.reset(new osgMyGUI::OSGTexture(texture)); mScreenshot->setRenderItemTexture(mScreenshotTexture.get()); - mScreenshot->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); + mScreenshot->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); } } diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index fbfbd0e702..a0833194b6 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -27,13 +27,6 @@ namespace { - std::string fpsLevelToStr(int level) - { - if (level == 0) - return "#{sOff}"; - else //if (level == 1) - return "#{sOn}"; - } std::string textureMipmappingToStr(const std::string& val) { @@ -130,12 +123,14 @@ namespace MWGui if (type == sliderType) { MyGUI::ScrollBar* scroll = current->castType(); + std::string valueStr; if (getSettingValueType(current) == "Float") { // TODO: ScrollBar isn't meant for this. should probably use a dedicated FloatSlider widget float min,max; getSettingMinMax(scroll, min, max); float value = Settings::Manager::getFloat(getSettingName(current), getSettingCategory(current)); + valueStr = MyGUI::utility::toString((int)value); value = std::max(min, std::min(value, max)); value = (value-min)/(max-min); @@ -144,15 +139,30 @@ namespace MWGui else { int value = Settings::Manager::getInt(getSettingName(current), getSettingCategory(current)); + valueStr = MyGUI::utility::toString(value); scroll->setScrollPosition(value); } scroll->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); + updateSliderLabel(scroll, valueStr); } configureWidgets(current); } } + void SettingsWindow::updateSliderLabel(MyGUI::ScrollBar *scroller, const std::string& value) + { + std::string labelWidgetName = scroller->getUserString("SettingLabelWidget"); + if (!labelWidgetName.empty()) + { + MyGUI::TextBox* textBox; + getWidget(textBox, labelWidgetName); + std::string labelCaption = scroller->getUserString("SettingLabelCaption"); + boost::algorithm::replace_all(labelCaption, "%s", value); + textBox->setCaptionWithReplacing(labelCaption); + } + } + SettingsWindow::SettingsWindow() : WindowBase("openmw_settings_window.layout"), mKeyboardMode(true) @@ -165,19 +175,11 @@ namespace MWGui getWidget(mOkButton, "OkButton"); getWidget(mResolutionList, "ResolutionList"); getWidget(mFullscreenButton, "FullscreenButton"); - getWidget(mVSyncButton, "VSyncButton"); getWidget(mWindowBorderButton, "WindowBorderButton"); - getWidget(mFOVSlider, "FOVSlider"); - getWidget(mAnisotropySlider, "AnisotropySlider"); getWidget(mTextureFilteringButton, "TextureFilteringButton"); - getWidget(mAnisotropyLabel, "AnisotropyLabel"); getWidget(mAnisotropyBox, "AnisotropyBox"); - getWidget(mShadersButton, "ShadersButton"); - getWidget(mShadowsEnabledButton, "ShadowsEnabledButton"); - getWidget(mShadowsTextureSize, "ShadowsTextureSize"); getWidget(mControlsBox, "ControlsBox"); getWidget(mResetControlsButton, "ResetControlsButton"); - getWidget(mDifficultySlider, "DifficultySlider"); getWidget(mKeyboardSwitch, "KeyboardButton"); getWidget(mControllerSwitch, "ControllerButton"); getWidget(mWaterTextureSize, "WaterTextureSize"); @@ -205,8 +207,6 @@ namespace MWGui mWaterTextureSize->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterTextureSizeChanged); - mShadowsTextureSize->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onShadowTextureSizeChanged); - mKeyboardSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onKeyboardSwitchClicked); mControllerSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onControllerSwitchClicked); @@ -238,7 +238,6 @@ namespace MWGui std::string tmip = Settings::Manager::getString("texture mipmap", "General"); mTextureFilteringButton->setCaption(textureMipmappingToStr(tmip)); - mAnisotropyLabel->setCaption("Anisotropy (" + MyGUI::utility::toString(Settings::Manager::getInt("anisotropy", "General")) + ")"); int waterTextureSize = Settings::Manager::getInt ("rtt size", "Water"); if (waterTextureSize >= 512) @@ -248,22 +247,6 @@ namespace MWGui if (waterTextureSize >= 2048) mWaterTextureSize->setIndexSelected(2); - mShadowsTextureSize->setCaption (Settings::Manager::getString ("texture size", "Shadows")); - - if (!Settings::Manager::getBool("shaders", "Objects")) - { - mShadowsEnabledButton->setEnabled(false); - } - - MyGUI::TextBox* fovText; - getWidget(fovText, "FovText"); - fovText->setCaption("Field of View (" + MyGUI::utility::toString(int(Settings::Manager::getInt("field of view", "Camera"))) + ")"); - - MyGUI::TextBox* diffText; - getWidget(diffText, "DifficultyText"); - - diffText->setCaptionWithReplacing("#{sDifficulty} (" + MyGUI::utility::toString(int(Settings::Manager::getInt("difficulty", "Game"))) + ")"); - mWindowBorderButton->setEnabled(!Settings::Manager::getBool("fullscreen", "Video")); mKeyboardSwitch->setStateSelected(true); @@ -343,12 +326,6 @@ namespace MWGui apply(); } - void SettingsWindow::onShadowTextureSizeChanged(MyGUI::ComboBox *_sender, size_t pos) - { - Settings::Manager::setString("texture size", "Shadows", _sender->getItemNameAt(pos)); - apply(); - } - void SettingsWindow::onButtonToggled(MyGUI::Widget* _sender) { std::string on = MWBase::Environment::get().getWindowManager()->getGameSettingString("sOn", "On"); @@ -365,21 +342,6 @@ namespace MWGui newState = true; } - if (_sender == mShadersButton) - { - if (newState == false) - { - // shadows not supported - mShadowsEnabledButton->setEnabled(false); - mShadowsEnabledButton->setCaptionWithReplacing("#{sOff}"); - Settings::Manager::setBool("enabled", "Shadows", false); - } - else - { - mShadowsEnabledButton->setEnabled(true); - } - } - if (_sender == mFullscreenButton) { // check if this resolution is supported in fullscreen @@ -439,6 +401,7 @@ namespace MWGui { if (getSettingType(scroller) == "Slider") { + std::string valueStr; if (getSettingValueType(scroller) == "Float") { float value = pos / float(scroller->getScrollRange()-1); @@ -447,28 +410,15 @@ namespace MWGui getSettingMinMax(scroller, min, max); value = min + (max-min) * value; Settings::Manager::setFloat(getSettingName(scroller), getSettingCategory(scroller), value); - - if (scroller == mFOVSlider) - { - MyGUI::TextBox* fovText; - getWidget(fovText, "FovText"); - fovText->setCaption("Field of View (" + MyGUI::utility::toString(int(value)) + ")"); - } - if (scroller == mDifficultySlider) - { - MyGUI::TextBox* diffText; - getWidget(diffText, "DifficultyText"); - diffText->setCaptionWithReplacing("#{sDifficulty} (" + MyGUI::utility::toString(int(value)) + ")"); - } + valueStr = MyGUI::utility::toString(int(value)); } else { Settings::Manager::setInt(getSettingName(scroller), getSettingCategory(scroller), pos); - if (scroller == mAnisotropySlider) - { - mAnisotropyLabel->setCaption("Anisotropy (" + MyGUI::utility::toString(pos) + ")"); - } + valueStr = MyGUI::utility::toString(pos); } + updateSliderLabel(scroller, valueStr); + apply(); } } diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 99553808b4..5b12cc557f 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -28,21 +28,12 @@ namespace MWGui // graphics MyGUI::ListBox* mResolutionList; MyGUI::Button* mFullscreenButton; - MyGUI::Button* mVSyncButton; MyGUI::Button* mWindowBorderButton; - MyGUI::ScrollBar* mFOVSlider; - MyGUI::ScrollBar* mDifficultySlider; - MyGUI::ScrollBar* mAnisotropySlider; MyGUI::ComboBox* mTextureFilteringButton; - MyGUI::TextBox* mAnisotropyLabel; MyGUI::Widget* mAnisotropyBox; - MyGUI::Button* mShadersButton; MyGUI::ComboBox* mWaterTextureSize; - MyGUI::Button* mShadowsEnabledButton; - MyGUI::ComboBox* mShadowsTextureSize; - // controls MyGUI::ScrollView* mControlsBox; MyGUI::Button* mResetControlsButton; @@ -62,8 +53,6 @@ namespace MWGui void onWaterTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos); - void onShadowTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos); - void onRebindAction(MyGUI::Widget* _sender); void onInputTabMouseWheel(MyGUI::Widget* _sender, int _rel); void onResetDefaultBindings(MyGUI::Widget* _sender); @@ -76,6 +65,7 @@ namespace MWGui void apply(); void configureWidgets(MyGUI::Widget* widget); + void updateSliderLabel(MyGUI::ScrollBar* scroller, const std::string& value); private: void resetScrollbars(); diff --git a/apps/openmw/mwgui/sortfilteritemmodel.cpp b/apps/openmw/mwgui/sortfilteritemmodel.cpp index 1d086f2ee3..9c73db3407 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.cpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.cpp @@ -132,9 +132,12 @@ namespace MWGui && !base.get()->mBase->mData.mIsScroll) return false; - if ((mFilter & Filter_OnlyUsableItems) && typeid(*base.getClass().use(base)) == typeid(MWWorld::NullAction) - && base.getClass().getScript(base).empty()) - return false; + if ((mFilter & Filter_OnlyUsableItems) && base.getClass().getScript(base).empty()) + { + boost::shared_ptr actionOnUse = base.getClass().use(base); + if (!actionOnUse || actionOnUse->isNullAction()) + return false; + } return true; } diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 6f45ed0053..e191c16bb4 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -4,8 +4,6 @@ #include #include -#include - #include #include "../mwbase/environment.hpp" @@ -19,8 +17,8 @@ #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/creaturestats.hpp" #include "inventorywindow.hpp" #include "itemview.hpp" @@ -323,78 +321,23 @@ namespace MWGui } } - // TODO: move to mwmechanics + bool offerAccepted = mTrading.haggle(player, mPtr, mCurrentBalance, mCurrentMerchantOffer); - // Is the player buying? - bool buying = (mCurrentMerchantOffer < 0); + // apply disposition change if merchant is NPC + if ( mPtr.getClass().isNpc() ) { + int dispositionDelta = offerAccepted + ? gmst.find("iBarterSuccessDisposition")->getInt() + : gmst.find("iBarterFailDisposition")->getInt(); - if(mCurrentBalance > mCurrentMerchantOffer) - { - //if npc is a creature: reject (no haggle) - if (mPtr.getTypeName() != typeid(ESM::NPC).name()) - { - MWBase::Environment::get().getWindowManager()-> - messageBox("#{sNotifyMessage9}"); - return; - } - - int a = abs(mCurrentMerchantOffer); - int b = abs(mCurrentBalance); - int d = 0; - if (buying) - d = int(100 * (a - b) / a); - else - d = int(100 * (b - a) / a); - - int clampedDisposition = std::max(0, std::min(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr) - + MWBase::Environment::get().getDialogueManager()->getTemporaryDispositionChange(),100)); - - const MWMechanics::CreatureStats &sellerStats = mPtr.getClass().getCreatureStats(mPtr); - const MWMechanics::CreatureStats &playerStats = player.getClass().getCreatureStats(player); - - float a1 = static_cast(player.getClass().getSkill(player, ESM::Skill::Mercantile)); - float b1 = 0.1f * playerStats.getAttribute(ESM::Attribute::Luck).getModified(); - float c1 = 0.2f * playerStats.getAttribute(ESM::Attribute::Personality).getModified(); - float d1 = static_cast(mPtr.getClass().getSkill(mPtr, ESM::Skill::Mercantile)); - float e1 = 0.1f * sellerStats.getAttribute(ESM::Attribute::Luck).getModified(); - float f1 = 0.2f * sellerStats.getAttribute(ESM::Attribute::Personality).getModified(); - - float dispositionTerm = gmst.find("fDispositionMod")->getFloat() * (clampedDisposition - 50); - float pcTerm = (dispositionTerm - 50 + a1 + b1 + c1) * playerStats.getFatigueTerm(); - float npcTerm = (d1 + e1 + f1) * sellerStats.getFatigueTerm(); - float x = gmst.find("fBargainOfferMulti")->getFloat() * d + gmst.find("fBargainOfferBase")->getFloat(); - if (buying) - x += abs(int(pcTerm - npcTerm)); - else - x += abs(int(npcTerm - pcTerm)); - - int roll = Misc::Rng::rollDice(100) + 1; - if(roll > x || (mCurrentMerchantOffer < 0) != (mCurrentBalance < 0)) //trade refused - { - MWBase::Environment::get().getWindowManager()-> - messageBox("#{sNotifyMessage9}"); - - int iBarterFailDisposition = gmst.find("iBarterFailDisposition")->getInt(); - if (mPtr.getClass().isNpc()) - MWBase::Environment::get().getDialogueManager()->applyDispositionChange(iBarterFailDisposition); - return; - } - - //skill use! - float skillGain = 0.f; - int finalPrice = std::abs(mCurrentBalance); - int initialMerchantOffer = std::abs(mCurrentMerchantOffer); - if (!buying && (finalPrice > initialMerchantOffer) && finalPrice > 0) - skillGain = floor(100 * (finalPrice - initialMerchantOffer) / float(finalPrice)); - else if (buying && (finalPrice < initialMerchantOffer) && initialMerchantOffer > 0) - skillGain = floor(100 * (initialMerchantOffer - finalPrice) / float(initialMerchantOffer)); - - player.getClass().skillUsageSucceeded(player, ESM::Skill::Mercantile, 0, skillGain); + MWBase::Environment::get().getDialogueManager()->applyDispositionChange(dispositionDelta); } - int iBarterSuccessDisposition = gmst.find("iBarterSuccessDisposition")->getInt(); - if (mPtr.getClass().isNpc()) - MWBase::Environment::get().getDialogueManager()->applyDispositionChange(iBarterSuccessDisposition); + // display message on haggle failure + if ( !offerAccepted ) { + MWBase::Environment::get().getWindowManager()-> + messageBox("#{sNotifyMessage9}"); + return; + } // make the item transfer mTradeModel->transferItems(); @@ -477,15 +420,15 @@ namespace MWGui // prevent overflows, and prevent entering INT_MIN since abs(INT_MIN) is undefined if (mCurrentBalance == INT_MAX || mCurrentBalance == INT_MIN+1) return; - if(mCurrentBalance<=-1) mCurrentBalance -= 1; - if(mCurrentBalance>=1) mCurrentBalance += 1; + if (mCurrentBalance < 0) mCurrentBalance -= 1; + else mCurrentBalance += 1; updateLabels(); } void TradeWindow::onDecreaseButtonTriggered() { - if(mCurrentBalance<-1) mCurrentBalance += 1; - if(mCurrentBalance>1) mCurrentBalance -= 1; + if (mCurrentBalance < 0) mCurrentBalance += 1; + else mCurrentBalance -= 1; updateLabels(); } @@ -496,13 +439,13 @@ namespace MWGui mPlayerGold->setCaptionWithReplacing("#{sYourGold} " + MyGUI::utility::toString(playerGold)); - if (mCurrentBalance > 0) + if (mCurrentBalance < 0) { - mTotalBalanceLabel->setCaptionWithReplacing("#{sTotalSold}"); + mTotalBalanceLabel->setCaptionWithReplacing("#{sTotalCost}"); } else { - mTotalBalanceLabel->setCaptionWithReplacing("#{sTotalCost}"); + mTotalBalanceLabel->setCaptionWithReplacing("#{sTotalSold}"); } mTotalBalance->setValue(std::abs(mCurrentBalance)); diff --git a/apps/openmw/mwgui/tradewindow.hpp b/apps/openmw/mwgui/tradewindow.hpp index a23196d70e..4b03c8d904 100644 --- a/apps/openmw/mwgui/tradewindow.hpp +++ b/apps/openmw/mwgui/tradewindow.hpp @@ -1,6 +1,8 @@ #ifndef MWGUI_TRADEWINDOW_H #define MWGUI_TRADEWINDOW_H +#include "../mwmechanics/trading.hpp" + #include "referenceinterface.hpp" #include "windowbase.hpp" @@ -40,6 +42,7 @@ namespace MWGui ItemView* mItemView; SortFilterItemModel* mSortModel; TradeItemModel* mTradeModel; + MWMechanics::Trading mTrading; static const float sBalanceChangeInitialPause; // in seconds static const float sBalanceChangeInterval; // in seconds diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index 30ad27e46e..82218fef12 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -176,9 +176,9 @@ namespace MWGui MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); // advance time + MWBase::Environment::get().getMechanicsManager()->rest(false); + MWBase::Environment::get().getMechanicsManager()->rest(false); MWBase::Environment::get().getWorld ()->advanceTime (2); - MWBase::Environment::get().getMechanicsManager()->rest(false); - MWBase::Environment::get().getMechanicsManager()->rest(false); mProgressBar.setVisible(true); mProgressBar.setProgress(0, 2); diff --git a/apps/openmw/mwgui/videowidget.cpp b/apps/openmw/mwgui/videowidget.cpp index d28ea0b660..5a4bb981f4 100644 --- a/apps/openmw/mwgui/videowidget.cpp +++ b/apps/openmw/mwgui/videowidget.cpp @@ -50,7 +50,7 @@ void VideoWidget::playVideo(const std::string &video) mTexture.reset(new osgMyGUI::OSGTexture(texture)); setRenderItemTexture(mTexture.get()); - getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); + getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); } int VideoWidget::getVideoWidth() diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index e8ac63bf6b..8685475a4e 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -189,8 +189,12 @@ namespace MWGui void WaitDialog::onWaitingProgressChanged(int cur, int total) { mProgressBar.setProgress(cur, total); - MWBase::Environment::get().getWorld()->advanceTime(1); MWBase::Environment::get().getMechanicsManager()->rest(mSleeping); + MWBase::Environment::get().getWorld()->advanceTime(1); + + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + if (player.getClass().getCreatureStats(player).isDead()) + stopWaiting(); } void WaitDialog::onWaitingInterrupted() diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index f151bee80b..54fd31acda 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -28,7 +28,7 @@ #include #include -#include +#include #include @@ -194,7 +194,7 @@ namespace MWGui , mVersionDescription(versionDescription) { float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); - mGuiPlatform = new osgMyGUI::Platform(viewer, guiRoot, resourceSystem->getTextureManager(), uiScale); + mGuiPlatform = new osgMyGUI::Platform(viewer, guiRoot, resourceSystem->getImageManager(), uiScale); mGuiPlatform->initialise(resourcePath, logpath); mGui = new MyGUI::Gui; @@ -926,7 +926,7 @@ namespace MWGui if (!mLocalMapRender) return; - MWWorld::Ptr player = MWMechanics::getPlayer(); + MWWorld::ConstPtr player = MWMechanics::getPlayer(); osg::Vec3f playerPosition = player.getRefData().getPosition().asVec3(); osg::Quat playerOrientation (-player.getRefData().getPosition().rot[2], osg::Vec3(0,0,1)); @@ -938,10 +938,9 @@ namespace MWGui if (!player.getCell()->isExterior()) { - mMap->setActiveCell(x, y, true); - mHud->setActiveCell(x, y, true); + setActiveMap(x, y, true); } - // else: need to know the current grid center, call setActiveCell from MWWorld::Scene + // else: need to know the current grid center, call setActiveMap from changeCell mMap->setPlayerDir(playerdirection.x(), playerdirection.y()); mMap->setPlayerPos(x, y, u, v); @@ -1007,8 +1006,10 @@ namespace MWGui mDebugWindow->onFrame(frameDuration); } - void WindowManager::changeCell(MWWorld::CellStore* cell) + void WindowManager::changeCell(const MWWorld::CellStore* cell) { + mMap->requestMapRender(cell); + std::string name = MWBase::Environment::get().getWorld()->getCellName (cell); mMap->setCellName( name ); @@ -1020,6 +1021,8 @@ namespace MWGui mMap->addVisitedLocation (name, cell->getCell()->getGridX (), cell->getCell()->getGridY ()); mMap->cellExplored (cell->getCell()->getGridX(), cell->getCell()->getGridY()); + + setActiveMap(cell->getCell()->getGridX(), cell->getCell()->getGridY(), false); } else { @@ -1032,6 +1035,8 @@ namespace MWGui else MWBase::Environment::get().getWorld()->getPlayer().setLastKnownExteriorPosition(worldPos); mMap->setGlobalMapPlayerPosition(worldPos.x(), worldPos.y()); + + setActiveMap(0, 0, true); } } @@ -1630,9 +1635,7 @@ namespace MWGui layout->mMainWidget->setPosition(pos); layout->mMainWidget->setSize(size); - MyGUI::Window* window = dynamic_cast(layout->mMainWidget); - if (!window) - throw std::runtime_error("Attempting to track size of a non-resizable window"); + MyGUI::Window* window = layout->mMainWidget->castType(); window->eventWindowChangeCoord += MyGUI::newDelegate(this, &WindowManager::onWindowChangeCoord); mTrackedWindows[window] = name; } @@ -1999,29 +2002,33 @@ namespace MWGui return Misc::ResourceHelpers::correctTexturePath(path, mResourceSystem->getVFS()); } + bool WindowManager::textureExists(const std::string &path) + { + std::string corrected = Misc::ResourceHelpers::correctTexturePath(path, mResourceSystem->getVFS()); + return mResourceSystem->getVFS()->exists(corrected); + } + void WindowManager::createCursors() { MyGUI::ResourceManager::EnumeratorPtr enumerator = MyGUI::ResourceManager::getInstance().getEnumerator(); while (enumerator.next()) { MyGUI::IResource* resource = enumerator.current().second; - ResourceImageSetPointerFix* imgSetPointer = dynamic_cast(resource); + ResourceImageSetPointerFix* imgSetPointer = resource->castType(false); if (!imgSetPointer) continue; std::string tex_name = imgSetPointer->getImageSet()->getIndexInfo(0,0).texture; - osg::ref_ptr tex = mResourceSystem->getTextureManager()->getTexture2D(tex_name, osg::Texture::CLAMP, osg::Texture::CLAMP); + osg::ref_ptr image = mResourceSystem->getImageManager()->getImage(tex_name); - if(tex.valid()) + if(image.valid()) { //everything looks good, send it to the cursor manager - Uint8 size_x = imgSetPointer->getSize().width; - Uint8 size_y = imgSetPointer->getSize().height; Uint8 hotspot_x = imgSetPointer->getHotSpot().left; Uint8 hotspot_y = imgSetPointer->getHotSpot().top; int rotation = imgSetPointer->getRotation(); - mCursorManager->createCursor(imgSetPointer->getResourceName(), rotation, tex->getImage(), size_x, size_y, hotspot_x, hotspot_y); + mCursorManager->createCursor(imgSetPointer->getResourceName(), rotation, image, hotspot_x, hotspot_y); } } } @@ -2078,11 +2085,6 @@ namespace MWGui tex->unlock(); } - void WindowManager::requestMap(std::set cells) - { - mLocalMapRender->requestMap(cells); - } - void WindowManager::removeCell(MWWorld::CellStore *cell) { mLocalMapRender->removeCell(cell); diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 6edb4660cf..17a2e38555 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -204,7 +204,7 @@ namespace MWGui virtual void configureSkills (const SkillList& major, const SkillList& minor); ///< configure skill groups, each set contains the skill ID for that group. virtual void updateSkillArea(); ///< update display of skills, factions, birth sign, reputation and bounty - virtual void changeCell(MWWorld::CellStore* cell); ///< change the active cell + virtual void changeCell(const MWWorld::CellStore* cell); ///< change the active cell virtual void setFocusObject(const MWWorld::Ptr& focus); virtual void setFocusObjectScreenCoords(float min_x, float min_y, float max_x, float max_y); @@ -374,8 +374,8 @@ namespace MWGui virtual std::string correctIconPath(const std::string& path); virtual std::string correctBookartPath(const std::string& path, int width, int height); virtual std::string correctTexturePath(const std::string& path); + virtual bool textureExists(const std::string& path); - void requestMap(std::set cells); void removeCell(MWWorld::CellStore* cell); void writeFog(MWWorld::CellStore* cell); diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index a46f1778bb..6c2e695e6b 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -957,7 +957,7 @@ namespace MWInput if (!MWBase::Environment::get().getWindowManager()->getRestEnabled () || MWBase::Environment::get().getWindowManager()->isGuiMode ()) return; - if(mPlayer->isInCombat()) {//Check if in combat + if(mPlayer->enemiesNearby()) {//Check if in combat MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage2}"); //Nope, return; } @@ -1326,10 +1326,12 @@ namespace MWInput ICS::Control* c = mInputBinder->getChannel (action)->getAttachedControls ().front().control; - if (mInputBinder->getKeyBinding (c, ICS::Control::INCREASE) != SDL_SCANCODE_UNKNOWN) - return mInputBinder->scancodeToString (mInputBinder->getKeyBinding (c, ICS::Control::INCREASE)); - else if (mInputBinder->getMouseButtonBinding (c, ICS::Control::INCREASE) != ICS_MAX_DEVICE_BUTTONS) - return "#{sMouse} " + boost::lexical_cast(mInputBinder->getMouseButtonBinding (c, ICS::Control::INCREASE)); + SDL_Scancode key = mInputBinder->getKeyBinding (c, ICS::Control::INCREASE); + unsigned int mouse = mInputBinder->getMouseButtonBinding (c, ICS::Control::INCREASE); + if (key != SDL_SCANCODE_UNKNOWN) + return MyGUI::TextIterator::toTagsString(mInputBinder->scancodeToString (key)); + else if (mouse != ICS_MAX_DEVICE_BUTTONS) + return "#{sMouse} " + boost::lexical_cast(mouse); else return "#{sNone}"; } diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 6a247622c5..52c05fdfac 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -1,7 +1,6 @@ #include "activespells.hpp" #include - #include #include @@ -274,9 +273,7 @@ namespace MWMechanics for (std::vector::iterator effectIt = it->second.mEffects.begin(); effectIt != it->second.mEffects.end();) { - const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld()->getStore().get().find(effectIt->mEffectId); - if (effect->mData.mFlags & ESM::MagicEffect::CasterLinked - && it->second.mCasterActorId == casterActorId) + if (it->second.mCasterActorId == casterActorId) effectIt = it->second.mEffects.erase(effectIt); else ++effectIt; diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 3842ee61c5..0f1f803b73 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -5,13 +5,13 @@ #include #include +#include +#include + #include "../mwworld/timestamp.hpp" #include "magiceffects.hpp" -#include -#include - namespace MWMechanics { /// \brief Lasting spell effects diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 4046010d79..d12dde4244 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -6,6 +6,7 @@ #include #include #include + #include #include "../mwworld/esmstore.hpp" @@ -20,9 +21,9 @@ #include "../mwbase/dialoguemanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/statemanager.hpp" -#include "../mwmechanics/spellcasting.hpp" - +#include "spellcasting.hpp" #include "npcstats.hpp" #include "creaturestats.hpp" #include "movement.hpp" @@ -145,6 +146,9 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float namespace MWMechanics { + const float aiProcessingDistance = 7168; + const float sqrAiProcessingDistance = aiProcessingDistance*aiProcessingDistance; + class SoulTrap : public MWMechanics::EffectSourceVisitor { MWWorld::Ptr mCreature; @@ -291,7 +295,7 @@ namespace MWMechanics const ESM::Position& actor1Pos = actor1.getRefData().getPosition(); const ESM::Position& actor2Pos = actor2.getRefData().getPosition(); float sqrDist = (actor1Pos.asVec3() - actor2Pos.asVec3()).length2(); - if (sqrDist > 7168*7168) + if (sqrDist > sqrAiProcessingDistance) return; // pure water creatures won't try to fight with the target on the ground @@ -421,7 +425,9 @@ namespace MWMechanics DynamicStat magicka = creatureStats.getMagicka(); float diff = (static_cast(magickaFactor*intelligence)) - magicka.getBase(); - magicka.modify(diff); + float currentToBaseRatio = (magicka.getCurrent() / magicka.getBase()); + magicka.setModified(magicka.getModified() + diff, 0); + magicka.setCurrent(magicka.getBase() * currentToBaseRatio); creatureStats.setMagicka(magicka); } @@ -487,6 +493,31 @@ namespace MWMechanics stats.setFatigue (fatigue); } + class ExpiryVisitor : public EffectSourceVisitor + { + private: + MWWorld::Ptr mActor; + float mDuration; + + public: + ExpiryVisitor(const MWWorld::Ptr& actor, float duration) + : mActor(actor), mDuration(duration) + { + } + + virtual void visit (MWMechanics::EffectKey key, + const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/, + float magnitude, float remainingTime = -1, float /*totalTime*/ = -1) + { + if (magnitude > 0 && remainingTime > 0 && remainingTime < mDuration) + { + CreatureStats& creatureStats = mActor.getClass().getCreatureStats(mActor); + effectTick(creatureStats, mActor, key, magnitude * remainingTime); + creatureStats.getMagicEffects().add(key, -magnitude); + } + } + }; + void Actors::calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration) { CreatureStats &creatureStats = ptr.getClass().getCreatureStats(ptr); @@ -496,6 +527,11 @@ namespace MWMechanics if (duration > 0) { + // apply correct magnitude for tickable effects that have just expired, + // in case duration > remaining time of effect + ExpiryVisitor visitor(ptr, duration); + creatureStats.getActiveSpells().visitEffectSources(visitor); + for (MagicEffects::Collection::const_iterator it = effects.begin(); it != effects.end(); ++it) { // tickable effects (i.e. effects having a lasting impact after expiry) @@ -507,6 +543,7 @@ namespace MWMechanics CastSpell cast(ptr, ptr); if (cast.applyInstantEffect(ptr, ptr, it->first, it->second.getMagnitude())) { + creatureStats.getSpells().purgeEffect(it->first.mId); creatureStats.getActiveSpells().purgeEffect(it->first.mId); if (ptr.getClass().hasInventoryStore(ptr)) ptr.getClass().getInventoryStore(ptr).purgeEffect(it->first.mId); @@ -526,6 +563,9 @@ namespace MWMechanics creatureStats.setAttribute(i, stat); } + if (creatureStats.needToRecalcDynamicStats()) + calculateDynamicStats(ptr); + { Spells & spells = creatureStats.getSpells(); for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it) @@ -549,8 +589,9 @@ namespace MWMechanics DynamicStat stat = creatureStats.getDynamic(i); stat.setModifier(effects.get(ESM::MagicEffect::FortifyHealth+i).getMagnitude() - effects.get(ESM::MagicEffect::DrainHealth+i).getMagnitude(), + // Magicka can be decreased below zero due to a fortify effect wearing off // Fatigue can be decreased below zero meaning the actor will be knocked out - i == 2); + i == 1 || i == 2); creatureStats.setDynamic(i, stat); } @@ -684,7 +725,7 @@ namespace MWMechanics creatureStats.getActiveSpells().visitEffectSources(updateSummonedCreatures); if (ptr.getClass().hasInventoryStore(ptr)) ptr.getClass().getInventoryStore(ptr).visitEffectSources(updateSummonedCreatures); - updateSummonedCreatures.finish(); + updateSummonedCreatures.process(); } void Actors::calculateNpcStatModifiers (const MWWorld::Ptr& ptr, float duration) @@ -974,19 +1015,17 @@ namespace MWMechanics int hostilesCount = 0; // need to know this to play Battle music - // AI processing is only done within distance of 7168 units to the player. Note the "AI distance" slider doesn't affect this - // (it only does some throttling for targets beyond the "AI distance", so doesn't give any guarantees as to whether AI will be enabled or not) - // This distance could be made configurable later, but the setting must be marked with a big warning: - // using higher values will make a quest in Bloodmoon harder or impossible to complete (bug #1876) - const float sqrProcessingDistance = 7168*7168; - /// \todo move update logic to Actor class where appropriate // AI and magic effects update for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { + // AI processing is only done within distance of 7168 units to the player. Note the "AI distance" slider doesn't affect this + // (it only does some throttling for targets beyond the "AI distance", so doesn't give any guarantees as to whether AI will be enabled or not) + // This distance could be made configurable later, but the setting must be marked with a big warning: + // using higher values will make a quest in Bloodmoon harder or impossible to complete (bug #1876) bool inProcessingRange = (player.getRefData().getPosition().asVec3() - iter->first.getRefData().getPosition().asVec3()).length2() - <= sqrProcessingDistance; + <= sqrAiProcessingDistance; iter->second->getCharacterController()->setActive(inProcessingRange); @@ -995,7 +1034,13 @@ namespace MWMechanics if (!iter->first.getClass().getCreatureStats(iter->first).isDead()) { - updateActor(iter->first, duration); + MWWorld::Ptr actor = iter->first; // make a copy of the map key to avoid it being invalidated when the player teleports + updateActor(actor, duration); + if (MWBase::Environment::get().getWorld()->hasCellChanged()) + { + return; // for now abort update of the old cell when cell changes by teleportation magic effect + // a better solution might be to apply cell changes at the end of the frame + } if (MWBase::Environment::get().getMechanicsManager()->isAIActive() && inProcessingRange) { if (timerUpdateAITargets == 0) @@ -1059,7 +1104,7 @@ namespace MWMechanics { if (iter->first != player && (player.getRefData().getPosition().asVec3() - iter->first.getRefData().getPosition().asVec3()).length2() - > sqrProcessingDistance) + > sqrAiProcessingDistance) continue; if (iter->first.getClass().getCreatureStats(iter->first).isParalyzed()) @@ -1138,14 +1183,19 @@ namespace MWMechanics for (PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { + MWWorld::Ptr observer = iter->first; + if (iter->first == player) // not the player continue; + if (observer.getClass().getCreatureStats(observer).isDead()) + continue; + // is the player in range and can they be detected - if ((iter->first.getRefData().getPosition().asVec3() - player.getRefData().getPosition().asVec3()).length2() <= radius*radius - && MWBase::Environment::get().getWorld()->getLOS(player, iter->first)) + if ((observer.getRefData().getPosition().asVec3() - player.getRefData().getPosition().asVec3()).length2() <= radius*radius + && MWBase::Environment::get().getWorld()->getLOS(player, observer)) { - if (MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, iter->first)) + if (MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, observer)) { detected = true; avoidedNotice = false; @@ -1197,25 +1247,14 @@ namespace MWMechanics continue; } - if (iter->second->getCharacterController()->kill()) + CharacterController::KillResult killResult = iter->second->getCharacterController()->kill(); + if (killResult == CharacterController::Result_DeathAnimStarted) { - // TODO: It's not known whether the soundgen tags scream, roar, and moan are reliable - // for NPCs since some of the npc death animation files are missing them. - // Play dying words + // Note: It's not known whether the soundgen tags scream, roar, and moan are reliable + // for NPCs since some of the npc death animation files are missing them. MWBase::Environment::get().getDialogueManager()->say(iter->first, "hit"); - iter->first.getClass().getCreatureStats(iter->first).notifyDied(); - - ++mDeathCount[Misc::StringUtils::lowerCase(iter->first.getCellRef().getRefId())]; - - // Make sure spell effects with CasterLinked flag are removed - for (PtrActorMap::iterator iter2(mActors.begin());iter2 != mActors.end();++iter2) - { - MWMechanics::ActiveSpells& spells = iter2->first.getClass().getCreatureStats(iter2->first).getActiveSpells(); - spells.purge(stats.getActorId()); - } - // Apply soultrap if (iter->first.getTypeName() == typeid(ESM::Creature).name()) { @@ -1234,13 +1273,87 @@ namespace MWMechanics if (cls.isEssential(iter->first)) MWBase::Environment::get().getWindowManager()->messageBox("#{sKilledEssential}"); } + else if (killResult == CharacterController::Result_DeathAnimJustFinished) + { + iter->first.getClass().getCreatureStats(iter->first).notifyDied(); + + ++mDeathCount[Misc::StringUtils::lowerCase(iter->first.getCellRef().getRefId())]; + + // Make sure spell effects are removed + purgeSpellEffects(stats.getActorId()); + + if( iter->first == getPlayer()) + { + //player's death animation is over + MWBase::Environment::get().getStateManager()->askLoadRecent(); + } + + // Play Death Music if it was the player dying + if(iter->first == getPlayer()) + MWBase::Environment::get().getSoundManager()->streamMusic("Special/MW_Death.mp3"); + } } } - void Actors::restoreDynamicStats(bool sleep) + void Actors::cleanupSummonedCreature (MWMechanics::CreatureStats& casterStats, int creatureActorId) { - for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter) + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(creatureActorId); + if (!ptr.isEmpty()) + { + MWBase::Environment::get().getWorld()->deleteObject(ptr); + + const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() + .search("VFX_Summon_End"); + if (fx) + MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel, + "", ptr.getRefData().getPosition().asVec3()); + } + else if (creatureActorId != -1) + { + // We didn't find the creature. It's probably in an inactive cell. + // Add to graveyard so we can delete it when the cell becomes active. + std::vector& graveyard = casterStats.getSummonedCreatureGraveyard(); + graveyard.push_back(creatureActorId); + } + + purgeSpellEffects(creatureActorId); + } + + void Actors::purgeSpellEffects(int casterActorId) + { + for (PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter) + { + MWMechanics::ActiveSpells& spells = iter->first.getClass().getCreatureStats(iter->first).getActiveSpells(); + spells.purge(casterActorId); + } + } + + void Actors::rest(bool sleep) + { + float duration = 3600.f / MWBase::Environment::get().getWorld()->getTimeScaleFactor(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + + for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) + { + if (iter->first.getClass().getCreatureStats(iter->first).isDead()) + continue; + restoreDynamicStats(iter->first, sleep); + + if ((!iter->first.getRefData().getBaseNode()) || + (player.getRefData().getPosition().asVec3() - iter->first.getRefData().getPosition().asVec3()).length2() > sqrAiProcessingDistance) + continue; + + adjustMagicEffects (iter->first); + if (iter->first.getClass().getCreatureStats(iter->first).needToRecalcDynamicStats()) + calculateDynamicStats (iter->first); + + calculateCreatureStatModifiers (iter->first, duration); + if (iter->first.getClass().isNpc()) + calculateNpcStatModifiers(iter->first, duration); + } + + fastForwardAi(); } int Actors::getHoursToRest(const MWWorld::Ptr &ptr) const @@ -1372,7 +1485,7 @@ namespace MWMechanics { if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdFollow) { - MWWorld::Ptr followTarget = static_cast(*it)->getTarget(); + MWWorld::Ptr followTarget = (*it)->getTarget(); if (followTarget.isEmpty()) continue; if (followTarget == actor) @@ -1386,23 +1499,46 @@ namespace MWMechanics return list; } + + std::list Actors::getActorsFighting(const MWWorld::Ptr& actor) { std::list list; std::vector neighbors; osg::Vec3f position (actor.getRefData().getPosition().asVec3()); - getObjectsInRange(position, - MWBase::Environment::get().getWorld()->getStore().get().find("fAlarmRadius")->getFloat(), - neighbors); //only care about those within the alarm disance - for(std::vector::iterator iter(neighbors.begin());iter != neighbors.end();++iter) + getObjectsInRange(position, aiProcessingDistance, neighbors); + for(std::vector::const_iterator iter(neighbors.begin());iter != neighbors.end();++iter) { const MWWorld::Class &cls = iter->getClass(); - CreatureStats &stats = cls.getCreatureStats(*iter); - if (!stats.isDead() && stats.getAiSequence().isInCombat(actor)) + const CreatureStats &stats = cls.getCreatureStats(*iter); + if (stats.isDead() || *iter == actor) + continue; + if (stats.getAiSequence().isInCombat(actor)) list.push_front(*iter); } return list; } + std::list Actors::getEnemiesNearby(const MWWorld::Ptr& actor) + { + std::list list; + std::vector neighbors; + osg::Vec3f position (actor.getRefData().getPosition().asVec3()); + getObjectsInRange(position, aiProcessingDistance, neighbors); + + std::list followers = getActorsFollowing(actor); + for(std::vector::const_iterator iter(neighbors.begin());iter != neighbors.end();++iter) + { + const CreatureStats &stats = iter->getClass().getCreatureStats(*iter); + if (stats.isDead() || *iter == actor) + continue; + const bool isFollower = std::find(followers.begin(), followers.end(), *iter) != followers.end(); + if (stats.getAiSequence().isInCombat(actor) || (MWBase::Environment::get().getMechanicsManager()->isAggressive(*iter, actor) && !isFollower)) + list.push_back(*iter); + } + return list; + } + + void Actors::write (ESM::ESMWriter& writer, Loading::Listener& listener) const { writer.startRecord(ESM::REC_DCOU); diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 0bdf611d2a..0dc684c564 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -7,9 +7,10 @@ #include #include -#include "movement.hpp" #include "../mwbase/world.hpp" +#include "movement.hpp" + namespace MWWorld { class Ptr; @@ -19,6 +20,7 @@ namespace MWWorld namespace MWMechanics { class Actor; + class CreatureStats; class Actors { @@ -43,6 +45,8 @@ namespace MWMechanics void killDeadActors (); + void purgeSpellEffects (int casterActorId); + public: Actors(); @@ -89,8 +93,8 @@ namespace MWMechanics void updateHeadTracking(const MWWorld::Ptr& actor, const MWWorld::Ptr& targetActor, MWWorld::Ptr& headTrackTarget, float& sqrHeadTrackDistance); - void restoreDynamicStats(bool sleep); - ///< If the player is sleeping, this should be called every hour. + void rest(bool sleep); + ///< Update actors while the player is waiting or sleeping. This should be called every hour. void restoreDynamicStats(const MWWorld::Ptr& actor, bool sleep); @@ -111,6 +115,8 @@ namespace MWMechanics void getObjectsInRange(const osg::Vec3f& position, float radius, std::vector& out); + void cleanupSummonedCreature (CreatureStats& casterStats, int creatureActorId); + ///Returns the list of actors which are siding with the given actor in fights /**ie AiFollow or AiEscort is active and the target is the actor **/ std::list getActorsSidingWith(const MWWorld::Ptr& actor); @@ -123,6 +129,9 @@ namespace MWMechanics /**ie AiCombat is active and the target is the actor **/ std::list getActorsFighting(const MWWorld::Ptr& actor); + /// Unlike getActorsFighting, also returns actors that *would* fight the given actor if they saw him. + std::list getEnemiesNearby(const MWWorld::Ptr& actor); + void write (ESM::ESMWriter& writer, Loading::Listener& listener) const; void readRecord (ESM::ESMReader& reader, uint32_t type); diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index b3c011d500..4063632430 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -5,66 +5,69 @@ #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" -#include "../mwmechanics/creaturestats.hpp" - #include "../mwworld/class.hpp" +#include "creaturestats.hpp" #include "steering.hpp" #include "movement.hpp" -MWMechanics::AiActivate::AiActivate(const std::string &objectId) - : mObjectId(objectId) +namespace MWMechanics { -} -MWMechanics::AiActivate *MWMechanics::AiActivate::clone() const -{ - return new AiActivate(*this); -} -bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) -{ - const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mObjectId, false); //The target to follow + AiActivate::AiActivate(const std::string &objectId) + : mObjectId(objectId) + { + } - actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); + AiActivate *MWMechanics::AiActivate::clone() const + { + return new AiActivate(*this); + } - if(target == MWWorld::Ptr() || - !target.getRefData().getCount() || !target.getRefData().isEnabled() // Really we should be checking whether the target is currently registered - // with the MechanicsManager + bool AiActivate::execute(const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) + { + const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mObjectId, false); //The target to follow + + actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); + + if (target == MWWorld::Ptr() || + !target.getRefData().getCount() || !target.getRefData().isEnabled() // Really we should check whether the target is currently registered + // with the MechanicsManager ) return true; //Target doesn't exist - //Set the target destination for the actor - ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; + //Set the target destination for the actor + ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; - if (pathTo(actor, dest, duration, 200)) //Go to the destination - { - // activate when reached - MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mObjectId,false); - MWBase::Environment::get().getWorld()->activate(target, actor); + if (pathTo(actor, dest, duration, 200)) //Go to the destination + { + // activate when reached + MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mObjectId,false); + MWBase::Environment::get().getWorld()->activate(target, actor); - return true; + return true; + } + + return false; } - return false; -} - -int MWMechanics::AiActivate::getTypeId() const -{ - return TypeIdActivate; -} - -void MWMechanics::AiActivate::writeState(ESM::AiSequence::AiSequence &sequence) const -{ - std::auto_ptr activate(new ESM::AiSequence::AiActivate()); - activate->mTargetId = mObjectId; - - ESM::AiSequence::AiPackageContainer package; - package.mType = ESM::AiSequence::Ai_Activate; - package.mPackage = activate.release(); - sequence.mPackages.push_back(package); -} - -MWMechanics::AiActivate::AiActivate(const ESM::AiSequence::AiActivate *activate) - : mObjectId(activate->mTargetId) -{ - + int AiActivate::getTypeId() const + { + return TypeIdActivate; + } + + void AiActivate::writeState(ESM::AiSequence::AiSequence &sequence) const + { + std::auto_ptr activate(new ESM::AiSequence::AiActivate()); + activate->mTargetId = mObjectId; + + ESM::AiSequence::AiPackageContainer package; + package.mType = ESM::AiSequence::Ai_Activate; + package.mPackage = activate.release(); + sequence.mPackages.push_back(package); + } + + AiActivate::AiActivate(const ESM::AiSequence::AiActivate *activate) + : mObjectId(activate->mTargetId) + { + } } diff --git a/apps/openmw/mwmechanics/aiactivate.hpp b/apps/openmw/mwmechanics/aiactivate.hpp index 2ca985be9a..8de4be69fa 100644 --- a/apps/openmw/mwmechanics/aiactivate.hpp +++ b/apps/openmw/mwmechanics/aiactivate.hpp @@ -2,6 +2,7 @@ #define GAME_MWMECHANICS_AIACTIVATE_H #include "aipackage.hpp" + #include #include "pathfinding.hpp" diff --git a/apps/openmw/mwmechanics/aiavoiddoor.cpp b/apps/openmw/mwmechanics/aiavoiddoor.cpp index 100b666e89..bb03ff53b2 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.cpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.cpp @@ -3,12 +3,12 @@ #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" + #include "../mwworld/class.hpp" + #include "creaturestats.hpp" #include "movement.hpp" #include "actorutil.hpp" - - #include "steering.hpp" MWMechanics::AiAvoidDoor::AiAvoidDoor(const MWWorld::ConstPtr& doorPtr) diff --git a/apps/openmw/mwmechanics/aiavoiddoor.hpp b/apps/openmw/mwmechanics/aiavoiddoor.hpp index 9d63c63e08..7344a1797c 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.hpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.hpp @@ -2,11 +2,15 @@ #define GAME_MWMECHANICS_AIAVOIDDOOR_H #include "aipackage.hpp" + #include -#include "pathfinding.hpp" + #include + #include "../mwworld/class.hpp" +#include "pathfinding.hpp" + namespace MWMechanics { /// \brief AiPackage to have an actor avoid an opening door @@ -26,6 +30,9 @@ namespace MWMechanics virtual unsigned int getPriority() const; + virtual bool canCancel() const { return false; } + virtual bool shouldCancelPreviousAi() const { return false; } + private: float mDuration; MWWorld::ConstPtr mDoorPtr; diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index acdbef299f..e00a71f248 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -16,7 +16,6 @@ #include "steering.hpp" #include "movement.hpp" #include "character.hpp" - #include "aicombataction.hpp" #include "combat.hpp" @@ -67,9 +66,10 @@ namespace MWMechanics mActionCooldown(0), mStrength(), mForceNoShortcut(false), + mShortcutFailPos(), mMovement(), mAdjustAiming(false) - {} + {} void startCombatMove(bool isNpc, bool isDistantCombat, float distToTarget, float rangeAttack); void updateCombatMove(float duration); @@ -433,7 +433,7 @@ ESM::Weapon::AttackType chooseBestAttack(const ESM::Weapon* weapon, MWMechanics: float roll = Misc::Rng::rollClosedProbability(); if(roll <= 0.333f) //side punch { - movement.mPosition[0] = Misc::Rng::rollClosedProbability() ? 1.0f : -1.0f; + movement.mPosition[0] = (Misc::Rng::rollClosedProbability() < 0.5f) ? 1.0f : -1.0f; movement.mPosition[1] = 0; attackType = ESM::Weapon::AT_Slash; } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index ad683b505d..4be2ac9da7 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -3,16 +3,15 @@ #include "aipackage.hpp" -#include "pathfinding.hpp" - -#include "movement.hpp" -#include "obstacle.hpp" +#include #include "../mwworld/cellstore.hpp" // for Doors #include "../mwbase/world.hpp" -#include +#include "pathfinding.hpp" +#include "movement.hpp" +#include "obstacle.hpp" namespace ESM { @@ -53,6 +52,9 @@ namespace MWMechanics virtual void writeState(ESM::AiSequence::AiSequence &sequence) const; + virtual bool canCancel() const { return false; } + virtual bool shouldCancelPreviousAi() const { return false; } + private: int mTargetActorId; diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index 76d5ed9522..8953c8a2a7 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -1,5 +1,8 @@ #include "aicombataction.hpp" +#include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -8,11 +11,8 @@ #include "../mwworld/inventorystore.hpp" #include "../mwworld/actionequip.hpp" -#include "../mwmechanics/npcstats.hpp" -#include "../mwmechanics/spellcasting.hpp" - -#include -#include +#include "npcstats.hpp" +#include "spellcasting.hpp" namespace { @@ -94,7 +94,7 @@ namespace MWMechanics return rateEffects(potion->mEffects, actor, MWWorld::Ptr()); } - float rateWeapon (const MWWorld::Ptr &item, const MWWorld::Ptr& actor, const MWWorld::Ptr& target, int type, + float rateWeapon (const MWWorld::Ptr &item, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, int type, float arrowRating, float boltRating) { if (item.getTypeName() != typeid(ESM::Weapon).name()) @@ -106,6 +106,10 @@ namespace MWMechanics return 0.f; float rating=0.f; + float bonus=0.f; + + if (weapon->mData.mType >= ESM::Weapon::MarksmanBow && weapon->mData.mType <= ESM::Weapon::MarksmanThrown) + bonus+=1.5f; if (weapon->mData.mType >= ESM::Weapon::MarksmanBow) { @@ -150,21 +154,22 @@ namespace MWMechanics if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes && (item.getCellRef().getEnchantmentCharge() == -1 || item.getCellRef().getEnchantmentCharge() >= enchantment->mData.mCost)) - rating += rateEffects(enchantment->mEffects, actor, target); + rating += rateEffects(enchantment->mEffects, actor, enemy); } int skill = item.getClass().getEquipmentSkill(item); if (skill != -1) rating *= actor.getClass().getSkill(actor, skill) / 100.f; - return rating; + return rating + bonus; } - float rateSpell(const ESM::Spell *spell, const MWWorld::Ptr &actor, const MWWorld::Ptr& target) + float rateSpell(const ESM::Spell *spell, const MWWorld::Ptr &actor, const MWWorld::Ptr& enemy) { const CreatureStats& stats = actor.getClass().getCreatureStats(actor); - if (MWMechanics::getSpellSuccessChance(spell, actor) == 0) + float successChance = MWMechanics::getSpellSuccessChance(spell, actor); + if (successChance == 0.f) return 0.f; if (spell->mData.mType != ESM::Spell::ST_Spell) @@ -186,13 +191,13 @@ namespace MWMechanics int types = getRangeTypes(spell->mEffects); if ((types & Self) && stats.getActiveSpells().isSpellActive(spell->mId)) return 0.f; - if ( ((types & Touch) || (types & Target)) && target.getClass().getCreatureStats(target).getActiveSpells().isSpellActive(spell->mId)) + if ( ((types & Touch) || (types & Target)) && enemy.getClass().getCreatureStats(enemy).getActiveSpells().isSpellActive(spell->mId)) return 0.f; - return rateEffects(spell->mEffects, actor, target); + return rateEffects(spell->mEffects, actor, enemy) * (successChance / 100.f); } - float rateMagicItem(const MWWorld::Ptr &ptr, const MWWorld::Ptr &actor, const MWWorld::Ptr& target) + float rateMagicItem(const MWWorld::Ptr &ptr, const MWWorld::Ptr &actor, const MWWorld::Ptr& enemy) { if (ptr.getClass().getEnchantment(ptr).empty()) return 0.f; @@ -201,7 +206,7 @@ namespace MWMechanics if (enchantment->mData.mType == ESM::Enchantment::CastOnce) { - return rateEffects(enchantment->mEffects, actor, target); + return rateEffects(enchantment->mEffects, actor, enemy); } else { @@ -210,9 +215,9 @@ namespace MWMechanics } } - float rateEffect(const ESM::ENAMstruct &effect, const MWWorld::Ptr &actor, const MWWorld::Ptr &target) + float rateEffect(const ESM::ENAMstruct &effect, const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy) { - // NOTE: target may be empty + // NOTE: enemy may be empty float rating = 1; switch (effect.mEffectID) @@ -250,8 +255,36 @@ namespace MWMechanics case ESM::MagicEffect::CureCommonDisease: case ESM::MagicEffect::CureBlightDisease: case ESM::MagicEffect::CureCorprusDisease: + case ESM::MagicEffect::ResistBlightDisease: + case ESM::MagicEffect::ResistCommonDisease: + case ESM::MagicEffect::ResistCorprusDisease: case ESM::MagicEffect::Invisibility: + case ESM::MagicEffect::Chameleon: return 0.f; + + case ESM::MagicEffect::RestoreAttribute: + return 0.f; // TODO: implement based on attribute damage + case ESM::MagicEffect::RestoreSkill: + return 0.f; // TODO: implement based on skill damage + + case ESM::MagicEffect::ResistFire: + case ESM::MagicEffect::ResistFrost: + case ESM::MagicEffect::ResistMagicka: + case ESM::MagicEffect::ResistNormalWeapons: + case ESM::MagicEffect::ResistParalysis: + case ESM::MagicEffect::ResistPoison: + case ESM::MagicEffect::ResistShock: + return 0.f; // probably useless since we don't know in advance what the enemy will cast + + // don't cast these for now as they would make the NPC cast the same effect over and over again, especially when they have potions + case ESM::MagicEffect::FortifyAttribute: + case ESM::MagicEffect::FortifyHealth: + case ESM::MagicEffect::FortifyMagicka: + case ESM::MagicEffect::FortifyFatigue: + case ESM::MagicEffect::FortifySkill: + case ESM::MagicEffect::FortifyMaximumMagicka: + return 0.f; + case ESM::MagicEffect::Feather: if (actor.getClass().getEncumbrance(actor) - actor.getClass().getCapacity(actor) >= 0) return 100.f; @@ -314,7 +347,7 @@ namespace MWMechanics case ESM::MagicEffect::DamageAttribute: case ESM::MagicEffect::DrainAttribute: - if (!target.isEmpty() && target.getClass().getCreatureStats(target).getAttribute(effect.mAttribute).getModified() <= 0) + if (!enemy.isEmpty() && enemy.getClass().getCreatureStats(enemy).getAttribute(effect.mAttribute).getModified() <= 0) return 0.f; { if (effect.mAttribute >= 0 && effect.mAttribute < ESM::Attribute::Length) @@ -336,9 +369,9 @@ namespace MWMechanics case ESM::MagicEffect::DamageSkill: case ESM::MagicEffect::DrainSkill: - if (target.isEmpty() || !target.getClass().isNpc()) + if (enemy.isEmpty() || !enemy.getClass().isNpc()) return 0.f; - if (target.getClass().getNpcStats(target).getSkill(effect.mSkill).getModified() <= 0) + if (enemy.getClass().getNpcStats(enemy).getSkill(effect.mSkill).getModified() <= 0) return 0.f; break; @@ -346,9 +379,9 @@ namespace MWMechanics break; } - // TODO: for non-cumulative effects (e.g. paralyze), check if the target is already suffering from them + // TODO: for non-cumulative effects (e.g. paralyze), check if the enemy is already suffering from them - // TODO: could take into account target's resistance/weakness against the effect + // TODO: could take into account enemy's resistance/weakness against the effect const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effect.mEffectID); @@ -369,13 +402,13 @@ namespace MWMechanics return rating; } - float rateEffects(const ESM::EffectList &list, const MWWorld::Ptr& actor, const MWWorld::Ptr& target) + float rateEffects(const ESM::EffectList &list, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy) { - // NOTE: target may be empty + // NOTE: enemy may be empty float rating = 0.f; for (std::vector::const_iterator it = list.mList.begin(); it != list.mList.end(); ++it) { - rating += rateEffect(*it, actor, target); + rating += rateEffect(*it, actor, enemy); } return rating; } @@ -453,16 +486,20 @@ namespace MWMechanics { isRanged = false; + static const float fCombatDistance = MWBase::Environment::get().getWorld()->getStore().get().find("fCombatDistance")->getFloat(); + if (mWeapon.isEmpty()) { if (!mIsNpc) - return 200.f; // TODO: use true attack range (the same problem in Creature::hit) + { + return fCombatDistance; + } else { static float fHandToHandReach = MWBase::Environment::get().getWorld()->getStore().get().find("fHandToHandReach")->getFloat(); - return fHandToHandReach * 100.0f; + return fHandToHandReach * fCombatDistance; } } @@ -474,7 +511,7 @@ namespace MWMechanics return 1000.f; } else - return weapon->mData.mReach * 100.0f; + return weapon->mData.mReach * fCombatDistance; } const ESM::Weapon* ActionWeapon::getWeapon() const @@ -484,7 +521,7 @@ namespace MWMechanics return mWeapon.get()->mBase; } - boost::shared_ptr prepareNextAction(const MWWorld::Ptr &actor, const MWWorld::Ptr &target) + boost::shared_ptr prepareNextAction(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy) { Spells& spells = actor.getClass().getCreatureStats(actor).getSpells(); @@ -513,7 +550,7 @@ namespace MWMechanics for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { - float rating = rateMagicItem(*it, actor, target); + float rating = rateMagicItem(*it, actor, enemy); if (rating > bestActionRating) { bestActionRating = rating; @@ -525,7 +562,7 @@ namespace MWMechanics MWWorld::Ptr bestArrow; for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { - float rating = rateWeapon(*it, actor, target, ESM::Weapon::Arrow); + float rating = rateWeapon(*it, actor, enemy, ESM::Weapon::Arrow); if (rating > bestArrowRating) { bestArrowRating = rating; @@ -537,7 +574,7 @@ namespace MWMechanics MWWorld::Ptr bestBolt; for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { - float rating = rateWeapon(*it, actor, target, ESM::Weapon::Bolt); + float rating = rateWeapon(*it, actor, enemy, ESM::Weapon::Bolt); if (rating > bestBoltRating) { bestBoltRating = rating; @@ -552,7 +589,7 @@ namespace MWMechanics == equipmentSlots.end()) continue; - float rating = rateWeapon(*it, actor, target, -1, bestArrowRating, bestBoltRating); + float rating = rateWeapon(*it, actor, enemy, -1, bestArrowRating, bestBoltRating); if (rating > bestActionRating) { const ESM::Weapon* weapon = it->get()->mBase; @@ -573,7 +610,7 @@ namespace MWMechanics { const ESM::Spell* spell = it->first; - float rating = rateSpell(spell, actor, target); + float rating = rateSpell(spell, actor, enemy); if (rating > bestActionRating) { bestActionRating = rating; diff --git a/apps/openmw/mwmechanics/aicombataction.hpp b/apps/openmw/mwmechanics/aicombataction.hpp index 22f5434180..e4ce443464 100644 --- a/apps/openmw/mwmechanics/aicombataction.hpp +++ b/apps/openmw/mwmechanics/aicombataction.hpp @@ -3,11 +3,11 @@ #include +#include + #include "../mwworld/ptr.hpp" #include "../mwworld/containerstore.hpp" -#include - namespace MWMechanics { @@ -75,19 +75,19 @@ namespace MWMechanics virtual const ESM::Weapon* getWeapon() const; }; - float rateSpell (const ESM::Spell* spell, const MWWorld::Ptr& actor, const MWWorld::Ptr& target); - float rateMagicItem (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Ptr &target); + float rateSpell (const ESM::Spell* spell, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); + float rateMagicItem (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); float ratePotion (const MWWorld::Ptr& item, const MWWorld::Ptr &actor); /// @param type Skip all weapons that are not of this type (i.e. return rating 0) - float rateWeapon (const MWWorld::Ptr& item, const MWWorld::Ptr& actor, const MWWorld::Ptr& target, + float rateWeapon (const MWWorld::Ptr& item, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, int type=-1, float arrowRating=0.f, float boltRating=0.f); /// @note target may be empty - float rateEffect (const ESM::ENAMstruct& effect, const MWWorld::Ptr& actor, const MWWorld::Ptr& target); + float rateEffect (const ESM::ENAMstruct& effect, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); /// @note target may be empty - float rateEffects (const ESM::EffectList& list, const MWWorld::Ptr& actor, const MWWorld::Ptr& target); + float rateEffects (const ESM::EffectList& list, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); - boost::shared_ptr prepareNextAction (const MWWorld::Ptr& actor, const MWWorld::Ptr& target); + boost::shared_ptr prepareNextAction (const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); } #endif diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index baf4f1be77..2d7b8b9f67 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -10,13 +10,11 @@ #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" -#include "../mwmechanics/creaturestats.hpp" - +#include "creaturestats.hpp" #include "steering.hpp" #include "movement.hpp" /* - TODO: Test vanilla behavior on passing x0, y0, and z0 with duration of anything including 0. TODO: Different behavior for AIEscort a d x y z and AIEscortCell a c d x y z. TODO: Take account for actors being in different cells. */ @@ -24,29 +22,19 @@ namespace MWMechanics { AiEscort::AiEscort(const std::string &actorId, int duration, float x, float y, float z) - : mActorId(actorId), mX(x), mY(y), mZ(z), mRemainingDuration(static_cast(duration)) + : mActorId(actorId), mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast(duration)) , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { mMaxDist = 450; - - // The CS Help File states that if a duration is given, the AI package will run for that long - // BUT if a location is givin, it "trumps" the duration so it will simply escort to that location. - if(mX != 0 || mY != 0 || mZ != 0) - mRemainingDuration = 0; } AiEscort::AiEscort(const std::string &actorId, const std::string &cellId,int duration, float x, float y, float z) - : mActorId(actorId), mCellId(cellId), mX(x), mY(y), mZ(z), mRemainingDuration(static_cast(duration)) + : mActorId(actorId), mCellId(cellId), mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast(duration)) , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { mMaxDist = 450; - - // The CS Help File states that if a duration is given, the AI package will run for that long - // BUT if a location is given, it "trumps" the duration so it will simply escort to that location. - if(mX != 0 || mY != 0 || mZ != 0) - mRemainingDuration = 0; } AiEscort::AiEscort(const ESM::AiSequence::AiEscort *escort) @@ -56,6 +44,12 @@ namespace MWMechanics , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { + // mDuration isn't saved in the save file, so just giving it "1" for now if the package has a duration. + // The exact value of mDuration only matters for repeating packages + if (mRemainingDuration != 0) + mDuration = 1; + else + mDuration = 0; } @@ -68,16 +62,17 @@ namespace MWMechanics { // If AiEscort has ran for as long or longer then the duration specified // and the duration is not infinite, the package is complete. - if(mRemainingDuration != 0) + if(mDuration > 0) { - mRemainingDuration -= duration; - if (duration <= 0) + mRemainingDuration -= ((duration*MWBase::Environment::get().getWorld()->getTimeScaleFactor()) / 3600); + if (mRemainingDuration <= 0) + { + mRemainingDuration = mDuration; + mStarted = false; // Reset to false so this package will build path again when repeating return true; + } } - if (!isWithinMaxRange(osg::Vec3f(mX, mY, mZ), actor.getRefData().getPosition().asVec3())) - return false; - if (!mCellId.empty() && mCellId != actor.getCell()->getCell()->getCellId().mWorldspace) return false; // Not in the correct cell, pause and rely on the player to go back through a teleport door @@ -103,12 +98,16 @@ namespace MWMechanics point.mConnectionNum = 0; point.mUnknown = 0; if(pathTo(actor,point,duration)) //Returns true on path complete + { + mRemainingDuration = mDuration; + mStarted = false; // Reset to false so this package will build path again when repeating return true; + } mMaxDist = 450; } else { - // Stop moving if the player is to far away + // Stop moving if the player is too far away MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle3", 0, 1); actor.getClass().getMovementSettings(actor).mPosition[1] = 0; mMaxDist = 250; @@ -122,7 +121,7 @@ namespace MWMechanics return TypeIdEscort; } - MWWorld::Ptr AiEscort::getTarget() + MWWorld::Ptr AiEscort::getTarget() const { return MWBase::Environment::get().getWorld()->getPtr(mActorId, false); } @@ -142,5 +141,11 @@ namespace MWMechanics package.mPackage = escort.release(); sequence.mPackages.push_back(package); } + +void AiEscort::fastForward(const MWWorld::Ptr& actor, AiState &state) +{ + // Update duration counter + mRemainingDuration--; +} } diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp index cdb0f79360..7195822714 100644 --- a/apps/openmw/mwmechanics/aiescort.hpp +++ b/apps/openmw/mwmechanics/aiescort.hpp @@ -2,6 +2,7 @@ #define GAME_MWMECHANICS_AIESCORT_H #include "aipackage.hpp" + #include #include "pathfinding.hpp" @@ -37,11 +38,13 @@ namespace MWMechanics virtual int getTypeId() const; - MWWorld::Ptr getTarget(); + MWWorld::Ptr getTarget() const; virtual bool sideWithTarget() const { return true; } void writeState(ESM::AiSequence::AiSequence &sequence) const; + void fastForward(const MWWorld::Ptr& actor, AiState& state); + private: std::string mActorId; std::string mCellId; @@ -49,7 +52,8 @@ namespace MWMechanics float mY; float mZ; float mMaxDist; - float mRemainingDuration; // In seconds + float mDuration; // In hours + float mRemainingDuration; // In hours int mCellX; int mCellY; diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index 342ee97145..196498bad9 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -6,11 +6,12 @@ #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" + #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" + #include "creaturestats.hpp" #include "movement.hpp" - #include "steering.hpp" namespace MWMechanics @@ -28,30 +29,35 @@ struct AiFollowStorage : AiTemporaryBase int AiFollow::mFollowIndexCounter = 0; AiFollow::AiFollow(const std::string &actorId,float duration, float x, float y, float z) -: mAlwaysFollow(false), mCommanded(false), mRemainingDuration(duration), mX(x), mY(y), mZ(z) +: mAlwaysFollow(false), mCommanded(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) , mActorRefId(actorId), mActorId(-1), mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++) { } + AiFollow::AiFollow(const std::string &actorId,const std::string &cellId,float duration, float x, float y, float z) -: mAlwaysFollow(false), mCommanded(false), mRemainingDuration(duration), mX(x), mY(y), mZ(z) +: mAlwaysFollow(false), mCommanded(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) , mActorRefId(actorId), mActorId(-1), mCellId(cellId), mActive(false), mFollowIndex(mFollowIndexCounter++) { } AiFollow::AiFollow(const std::string &actorId, bool commanded) -: mAlwaysFollow(true), mCommanded(commanded), mRemainingDuration(0), mX(0), mY(0), mZ(0) +: mAlwaysFollow(true), mCommanded(commanded), mDuration(0), mRemainingDuration(0), mX(0), mY(0), mZ(0) , mActorRefId(actorId), mActorId(-1), mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++) { - } AiFollow::AiFollow(const ESM::AiSequence::AiFollow *follow) - : mAlwaysFollow(follow->mAlwaysFollow), mCommanded(follow->mCommanded), mRemainingDuration(follow->mRemainingDuration) + : mCommanded(follow->mCommanded), mRemainingDuration(follow->mRemainingDuration) , mX(follow->mData.mX), mY(follow->mData.mY), mZ(follow->mData.mZ) , mActorRefId(follow->mTargetId), mActorId(-1) , mCellId(follow->mCellId), mActive(follow->mActive), mFollowIndex(mFollowIndexCounter++) { - +// mDuration isn't saved in the save file, so just giving it "1" for now if the package has a duration. +// The exact value of mDuration only matters for repeating packages + if (mRemainingDuration != 0) + mDuration = 1; + else + mDuration = 0; } bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) @@ -101,11 +107,14 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte if(!mAlwaysFollow) //Update if you only follow for a bit { //Check if we've run out of time - if (mRemainingDuration != 0) + if (mDuration != 0) { - mRemainingDuration -= duration; - if (duration <= 0) + mRemainingDuration -= ((duration*MWBase::Environment::get().getWorld()->getTimeScaleFactor()) / 3600); + if (mRemainingDuration <= 0) + { + mRemainingDuration = mDuration; return true; + } } if((pos.pos[0]-mX)*(pos.pos[0]-mX) + @@ -128,8 +137,12 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte //Set the target destination from the actor ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; - const float threshold = 10; // to avoid constant switching between moving/stopping - if (!storage.mMoving) followDistance += threshold; + if (!storage.mMoving) + { + const float threshold = 10; // to avoid constant switching between moving/stopping + followDistance += threshold; + } + storage.mMoving = !pathTo(actor, dest, duration, followDistance); // Go to the destination if (storage.mMoving) @@ -185,7 +198,7 @@ void AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const sequence.mPackages.push_back(package); } -MWWorld::Ptr AiFollow::getTarget() +MWWorld::Ptr AiFollow::getTarget() const { if (mActorId == -2) return MWWorld::Ptr(); @@ -213,4 +226,10 @@ int AiFollow::getFollowIndex() const return mFollowIndex; } +void AiFollow::fastForward(const MWWorld::Ptr& actor, AiState &state) +{ + // Update duration counter + mRemainingDuration--; +} + } diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index 017ea7122d..e46205b951 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -2,10 +2,13 @@ #define GAME_MWMECHANICS_AIFOLLOW_H #include "aipackage.hpp" + #include -#include "pathfinding.hpp" + #include +#include "pathfinding.hpp" + namespace ESM { namespace AiSequence @@ -31,7 +34,7 @@ namespace MWMechanics AiFollow(const ESM::AiSequence::AiFollow* follow); - MWWorld::Ptr getTarget(); + MWWorld::Ptr getTarget() const; virtual bool sideWithTarget() const { return true; } virtual bool followTargetThroughDoors() const { return true; } @@ -50,17 +53,20 @@ namespace MWMechanics int getFollowIndex() const; + void fastForward(const MWWorld::Ptr& actor, AiState& state); + private: /// This will make the actor always follow. /** Thus ignoring mDuration and mX,mY,mZ (used for summoned creatures). **/ bool mAlwaysFollow; bool mCommanded; - float mRemainingDuration; // Seconds + float mDuration; // Hours + float mRemainingDuration; // Hours float mX; float mY; float mZ; std::string mActorRefId; - int mActorId; + mutable int mActorId; std::string mCellId; bool mActive; // have we spotted the target? int mFollowIndex; diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index f2d75001af..5345035f00 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -8,12 +8,13 @@ #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" + +#include "../mwworld/action.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" + #include "creaturestats.hpp" #include "movement.hpp" -#include "../mwworld/action.hpp" - #include "steering.hpp" #include "actorutil.hpp" #include "coordinateconverter.hpp" @@ -22,12 +23,13 @@ MWMechanics::AiPackage::~AiPackage() {} MWMechanics::AiPackage::AiPackage() : mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild + mStarted(false), mIsShortcutting(false), mShortcutProhibited(false), mShortcutFailPos() { } -MWWorld::Ptr MWMechanics::AiPackage::getTarget() +MWWorld::Ptr MWMechanics::AiPackage::getTarget() const { return MWWorld::Ptr(); } @@ -42,6 +44,21 @@ bool MWMechanics::AiPackage::followTargetThroughDoors() const return false; } +bool MWMechanics::AiPackage::canCancel() const +{ + return true; +} + +bool MWMechanics::AiPackage::shouldCancelPreviousAi() const +{ + return true; +} + +bool MWMechanics::AiPackage::getRepeat() const +{ + return false; +} + bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest, float duration, float destTolerance) { mTimer += duration; //Update timer @@ -73,7 +90,8 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr if (!mIsShortcutting) { - if (wasShortcutting || doesPathNeedRecalc(dest)) // only rebuild path if the target has moved + if (!mStarted // If repeating an AI package (mStarted = false), build a new path so package doesn't immediately end + || wasShortcutting || doesPathNeedRecalc(dest)) // if need to rebuild path { mPathFinder.buildSyncedPath(start, dest, actor.getCell()); @@ -94,7 +112,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr } } - if(!mPathFinder.getPath().empty()) //Path has points in it + if (!mPathFinder.getPath().empty()) //Path has points in it { ESM::Pathgrid::Point lastPos = mPathFinder.getPath().back(); //Get the end of the proposed path @@ -104,9 +122,10 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr } mTimer = 0; + mStarted = true; } - if (isDestReached || mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1])) //Path finished? + if (isDestReached || mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1])) // if path is finished { // turn to destination point zTurn(actor, getZAngleToPoint(start, dest)); diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 2dc8bd3c75..c6de396329 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -1,9 +1,9 @@ #ifndef GAME_MWMECHANICS_AIPACKAGE_H #define GAME_MWMECHANICS_AIPACKAGE_H -#include "pathfinding.hpp" #include +#include "pathfinding.hpp" #include "obstacle.hpp" #include "aistate.hpp" @@ -40,6 +40,9 @@ namespace MWMechanics TypeIdEscort = 2, TypeIdFollow = 3, TypeIdActivate = 4, + + // These 3 are not really handled as Ai Packages in the MW engine + // For compatibility do *not* return these in the getCurrentAiPackage script function.. TypeIdCombat = 5, TypeIdPursue = 6, TypeIdAvoidDoor = 7 @@ -71,7 +74,7 @@ namespace MWMechanics virtual void fastForward(const MWWorld::Ptr& actor, AiState& state) {} /// Get the target actor the AI is targeted at (not applicable to all AI packages, default return empty Ptr) - virtual MWWorld::Ptr getTarget(); + virtual MWWorld::Ptr getTarget() const; /// Return true if having this AiPackage makes the actor side with the target in fights (default false) virtual bool sideWithTarget() const; @@ -79,6 +82,15 @@ namespace MWMechanics /// Return true if the actor should follow the target through teleport doors (default false) virtual bool followTargetThroughDoors() const; + /// Can this Ai package be canceled? (default true) + virtual bool canCancel() const; + + /// Upon adding this Ai package, should the Ai Sequence attempt to cancel previous Ai packages (default true)? + virtual bool shouldCancelPreviousAi() const; + + /// Return true if this package should repeat. Currently only used for Wander packages. + virtual bool getRepeat() const; + bool isTargetMagicallyHidden(const MWWorld::Ptr& target); protected: @@ -111,18 +123,14 @@ namespace MWMechanics bool mShortcutProhibited; // shortcutting may be prohibited after unsuccessful attempt ESM::Pathgrid::Point mShortcutFailPos; // position of last shortcut fail - bool isWithinMaxRange(const osg::Vec3f& pos1, const osg::Vec3f& pos2) const - { - // Maximum travel distance for vanilla compatibility. - // Was likely meant to prevent NPCs walking into non-loaded exterior cells, but for some reason is used in interior cells as well. - // We can make this configurable at some point, but the default *must* be the below value. Anything else will break shoddily-written content (*cough* MW *cough*) in bizarre ways. - return (pos1 - pos2).length2() <= 7168*7168; - } + // Set to true once package starts actually being executed + bool mStarted; + + ESM::Pathgrid::Point mPrevDest; private: bool isNearInactiveCell(const ESM::Position& actorPos); }; - } #endif diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index 7909b02c91..2b218de037 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -8,8 +8,6 @@ #include "../mwworld/class.hpp" #include "../mwworld/action.hpp" -#include "../mwmechanics/creaturestats.hpp" - #include "movement.hpp" #include "creaturestats.hpp" diff --git a/apps/openmw/mwmechanics/aipursue.hpp b/apps/openmw/mwmechanics/aipursue.hpp index 813b87cff0..cb93e9636a 100644 --- a/apps/openmw/mwmechanics/aipursue.hpp +++ b/apps/openmw/mwmechanics/aipursue.hpp @@ -38,6 +38,9 @@ namespace MWMechanics virtual void writeState (ESM::AiSequence::AiSequence& sequence) const; + virtual bool canCancel() const { return false; } + virtual bool shouldCancelPreviousAi() const { return false; } + private: int mTargetActorId; // The actor to pursue diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index d55dc240e0..ed9db102f0 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -2,9 +2,13 @@ #include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + #include "aipackage.hpp" #include "aistate.hpp" - #include "aiwander.hpp" #include "aiescort.hpp" #include "aitravel.hpp" @@ -14,11 +18,6 @@ #include "aipursue.hpp" #include "actorutil.hpp" -#include - -#include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" - namespace MWMechanics { @@ -29,13 +28,14 @@ void AiSequence::copy (const AiSequence& sequence) mPackages.push_back ((*iter)->clone()); } -AiSequence::AiSequence() : mDone (false), mLastAiPackage(-1) {} +AiSequence::AiSequence() : mDone (false), mRepeat(false), mLastAiPackage(-1) {} AiSequence::AiSequence (const AiSequence& sequence) { copy (sequence); mDone = sequence.mDone; mLastAiPackage = sequence.mLastAiPackage; + mRepeat = sequence.mRepeat; } AiSequence& AiSequence::operator= (const AiSequence& sequence) @@ -68,9 +68,8 @@ bool AiSequence::getCombatTarget(MWWorld::Ptr &targetActor) const { if (getTypeId() != AiPackage::TypeIdCombat) return false; - const AiCombat *combat = static_cast(mPackages.front()); - targetActor = combat->getTarget(); + targetActor = mPackages.front()->getTarget(); return !targetActor.isEmpty(); } @@ -92,6 +91,8 @@ std::list::const_iterator AiSequence::erase(std::list::c { if (package == it) { + AiPackage* package = *it; + delete package; return mPackages.erase(it); } } @@ -114,8 +115,7 @@ bool AiSequence::isInCombat(const MWWorld::Ptr &actor) const { if ((*it)->getTypeId() == AiPackage::TypeIdCombat) { - const AiCombat *combat = static_cast(*it); - if (combat->getTarget() == actor) + if ((*it)->getTarget() == actor) return true; } } @@ -127,7 +127,10 @@ void AiSequence::stopCombat() for(std::list::iterator it = mPackages.begin(); it != mPackages.end(); ) { if ((*it)->getTypeId() == AiPackage::TypeIdCombat) + { + delete *it; it = mPackages.erase(it); + } else ++it; } @@ -138,7 +141,10 @@ void AiSequence::stopPursuit() for(std::list::iterator it = mPackages.begin(); it != mPackages.end(); ) { if ((*it)->getTypeId() == AiPackage::TypeIdPursue) + { + delete *it; it = mPackages.erase(it); + } else ++it; } @@ -149,86 +155,98 @@ bool AiSequence::isPackageDone() const return mDone; } +bool isActualAiPackage(int packageTypeId) +{ + return (packageTypeId != AiPackage::TypeIdCombat + && packageTypeId != AiPackage::TypeIdPursue + && packageTypeId != AiPackage::TypeIdAvoidDoor); +} + void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { if(actor != getPlayer()) { - if (!mPackages.empty()) + if (mPackages.empty()) { - MWMechanics::AiPackage* package = mPackages.front(); - mLastAiPackage = package->getTypeId(); + mLastAiPackage = -1; + return; + } - // if active package is combat one, choose nearest target - if (mLastAiPackage == AiPackage::TypeIdCombat) + MWMechanics::AiPackage* package = mPackages.front(); + int packageTypeId = package->getTypeId(); + // workaround ai packages not being handled as in the vanilla engine + if (isActualAiPackage(packageTypeId)) + mLastAiPackage = packageTypeId; + // if active package is combat one, choose nearest target + if (packageTypeId == AiPackage::TypeIdCombat) + { + std::list::iterator itActualCombat; + + float nearestDist = std::numeric_limits::max(); + osg::Vec3f vActorPos = actor.getRefData().getPosition().asVec3(); + + for(std::list::iterator it = mPackages.begin(); it != mPackages.end();) { - std::list::iterator itActualCombat; + if ((*it)->getTypeId() != AiPackage::TypeIdCombat) break; - float nearestDist = std::numeric_limits::max(); - osg::Vec3f vActorPos = actor.getRefData().getPosition().asVec3(); + MWWorld::Ptr target = static_cast(*it)->getTarget(); - for(std::list::iterator it = mPackages.begin(); it != mPackages.end();) + // target disappeared (e.g. summoned creatures) + if (target.isEmpty()) { - if ((*it)->getTypeId() != AiPackage::TypeIdCombat) break; - - MWWorld::Ptr target = static_cast(*it)->getTarget(); - - // target disappeared (e.g. summoned creatures) - if (target.isEmpty()) - { - delete *it; - it = mPackages.erase(it); - } - else - { - const ESM::Position &targetPos = target.getRefData().getPosition(); - - float distTo = (targetPos.asVec3() - vActorPos).length(); - - // Small threshold for changing target - if (it == mPackages.begin()) - distTo = std::max(0.f, distTo - 50.f); - - if (distTo < nearestDist) - { - nearestDist = distTo; - itActualCombat = it; - } - ++it; - } + delete *it; + it = mPackages.erase(it); } - - if (!mPackages.empty()) + else { - if (nearestDist < std::numeric_limits::max() && mPackages.begin() != itActualCombat) + const ESM::Position &targetPos = target.getRefData().getPosition(); + + float distTo = (targetPos.asVec3() - vActorPos).length(); + + // Small threshold for changing target + if (it == mPackages.begin()) + distTo = std::max(0.f, distTo - 50.f); + + if (distTo < nearestDist) { - // move combat package with nearest target to the front - mPackages.splice(mPackages.begin(), mPackages, itActualCombat); + nearestDist = distTo; + itActualCombat = it; } - - package = mPackages.front(); - mLastAiPackage = package->getTypeId(); - } - else - { - mDone = true; - return; + ++it; } } - if (package->execute (actor,characterController,state,duration)) + if (!mPackages.empty()) { - // To account for the rare case where AiPackage::execute() queued another AI package - // (e.g. AiPursue executing a dialogue script that uses startCombat) - std::list::iterator toRemove = - std::find(mPackages.begin(), mPackages.end(), package); - mPackages.erase(toRemove); - delete package; + if (nearestDist < std::numeric_limits::max() && mPackages.begin() != itActualCombat) + { + // move combat package with nearest target to the front + mPackages.splice(mPackages.begin(), mPackages, itActualCombat); + } + + package = mPackages.front(); + } + } + + if (package->execute (actor,characterController,state,duration)) + { + // Put repeating noncombat AI packages on the end of the stack so they can be used again + if (isActualAiPackage(packageTypeId) && (mRepeat || package->getRepeat())) + { + mPackages.push_back(package->clone()); + } + // To account for the rare case where AiPackage::execute() queued another AI package + // (e.g. AiPursue executing a dialogue script that uses startCombat) + std::list::iterator toRemove = + std::find(mPackages.begin(), mPackages.end(), package); + mPackages.erase(toRemove); + delete package; + if (isActualAiPackage(packageTypeId)) mDone = true; - } - else - { - mDone = false; - } + } + else + { + mDone = false; } } } @@ -251,13 +269,8 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor) // Notify AiWander of our current position so we can return to it after combat finished for (std::list::const_iterator iter (mPackages.begin()); iter!=mPackages.end(); ++iter) { - if((*iter)->getTypeId() == AiPackage::TypeIdPursue && package.getTypeId() == AiPackage::TypeIdPursue - && static_cast(*iter)->getTarget() == static_cast(&package)->getTarget()) - { - return; // target is already pursued - } if((*iter)->getTypeId() == AiPackage::TypeIdCombat && package.getTypeId() == AiPackage::TypeIdCombat - && static_cast(*iter)->getTarget() == static_cast(&package)->getTarget()) + && (*iter)->getTarget() == (&package)->getTarget()) { return; // already in combat with this actor } @@ -266,6 +279,23 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor) } } + // remove previous packages if required + if (package.shouldCancelPreviousAi()) + { + for(std::list::iterator it = mPackages.begin(); it != mPackages.end();) + { + if((*it)->canCancel()) + { + delete *it; + it = mPackages.erase(it); + } + else + ++it; + } + mRepeat=false; + } + + // insert new package in correct place depending on priority for(std::list::iterator it = mPackages.begin(); it != mPackages.end(); ++it) { if((*it)->getPriority() <= package.getPriority()) @@ -275,7 +305,7 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor) } } - mPackages.push_front (package.clone()); + mPackages.push_back (package.clone()); } AiPackage* MWMechanics::AiSequence::getActivePackage() @@ -288,6 +318,10 @@ AiPackage* MWMechanics::AiSequence::getActivePackage() void AiSequence::fill(const ESM::AIPackageList &list) { + // If there is more than one package in the list, enable repeating + if (!list.mList.empty() && list.mList.begin() != (list.mList.end()-1)) + mRepeat = true; + for (std::vector::const_iterator it = list.mList.begin(); it != list.mList.end(); ++it) { MWMechanics::AiPackage* package; @@ -337,63 +371,68 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) if (!sequence.mPackages.empty()) clear(); + // If there is more than one non-combat, non-pursue package in the list, enable repeating. + int count = 0; + for (std::vector::const_iterator it = sequence.mPackages.begin(); + it != sequence.mPackages.end(); ++it) + { + if (isActualAiPackage(it->mType)) + count++; + } + + if (count > 1) + mRepeat = true; + + // Load packages for (std::vector::const_iterator it = sequence.mPackages.begin(); it != sequence.mPackages.end(); ++it) { + std::auto_ptr package (NULL); switch (it->mType) { case ESM::AiSequence::Ai_Wander: { - MWMechanics::AiWander* wander = new AiWander( - static_cast(it->mPackage)); - mPackages.push_back(wander); + package.reset(new AiWander(static_cast(it->mPackage))); break; } case ESM::AiSequence::Ai_Travel: { - MWMechanics::AiTravel* travel = new AiTravel( - static_cast(it->mPackage)); - mPackages.push_back(travel); + package.reset(new AiTravel(static_cast(it->mPackage))); break; } case ESM::AiSequence::Ai_Escort: { - MWMechanics::AiEscort* escort = new AiEscort( - static_cast(it->mPackage)); - mPackages.push_back(escort); + package.reset(new AiEscort(static_cast(it->mPackage))); break; } case ESM::AiSequence::Ai_Follow: { - MWMechanics::AiFollow* follow = new AiFollow( - static_cast(it->mPackage)); - mPackages.push_back(follow); + package.reset(new AiFollow(static_cast(it->mPackage))); break; } case ESM::AiSequence::Ai_Activate: { - MWMechanics::AiActivate* activate = new AiActivate( - static_cast(it->mPackage)); - mPackages.push_back(activate); + package.reset(new AiActivate(static_cast(it->mPackage))); break; } case ESM::AiSequence::Ai_Combat: { - MWMechanics::AiCombat* combat = new AiCombat( - static_cast(it->mPackage)); - mPackages.push_back(combat); + package.reset(new AiCombat(static_cast(it->mPackage))); break; } case ESM::AiSequence::Ai_Pursue: { - MWMechanics::AiPursue* pursue = new AiPursue( - static_cast(it->mPackage)); - mPackages.push_back(pursue); + package.reset(new AiPursue(static_cast(it->mPackage))); break; } default: break; } + + if (!package.get()) + continue; + + mPackages.push_back(package.release()); } } diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 1eefe7c697..c7c32979ee 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -4,7 +4,6 @@ #include #include -//#include "aistate.hpp" namespace MWWorld { @@ -40,6 +39,9 @@ namespace MWMechanics ///Finished with top AIPackage, set for one frame bool mDone; + ///Does this AI sequence repeat (repeating of Wander packages handled separately) + bool mRepeat; + ///Copy AiSequence void copy (const AiSequence& sequence); diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index c29861f4ee..5375644be5 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -13,6 +13,18 @@ #include "movement.hpp" #include "creaturestats.hpp" +namespace +{ + +bool isWithinMaxRange(const osg::Vec3f& pos1, const osg::Vec3f& pos2) +{ + // Maximum travel distance for vanilla compatibility. + // Was likely meant to prevent NPCs walking into non-loaded exterior cells, but for some reason is used in interior cells as well. + // We can make this configurable at some point, but the default *must* be the below value. Anything else will break shoddily-written content (*cough* MW *cough*) in bizarre ways. + return (pos1 - pos2).length2() <= 7168*7168; +} + +} namespace MWMechanics { @@ -46,9 +58,13 @@ namespace MWMechanics if (!isWithinMaxRange(osg::Vec3f(mX, mY, mZ), pos.asVec3())) return false; + if (!mStarted) + mStarted = true; + if (pathTo(actor, ESM::Pathgrid::Point(static_cast(mX), static_cast(mY), static_cast(mZ)), duration)) { actor.getClass().getMovementSettings(actor).mPosition[1] = 0; + mStarted = false; // Reset to false so this package will build path again when repeating return true; } return false; diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index d7dcad2dd5..7afbedb126 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -66,10 +66,26 @@ namespace MWMechanics // AiWander states AiWander::WanderState mState; + + bool mIsWanderingManually; + bool mCanWanderAlongPathGrid; unsigned short mIdleAnimation; std::vector mBadIdles; // Idle animations that when called cause errors + // do we need to calculate allowed nodes based on mDistance + bool mPopulateAvailableNodes; + + // allowed pathgrid nodes based on mDistance from the spawn point + // in local coordinates of mCell + std::vector mAllowedNodes; + + ESM::Pathgrid::Point mCurrentNode; + bool mTrimCurrentNode; + + float mDoorCheckDuration; + int mStuckCount; + AiWanderStorage(): mTargetAngleRadians(0), mTurnActorGivingGreetingToFacePlayer(false), @@ -78,14 +94,26 @@ namespace MWMechanics mGreetingTimer(0), mCell(NULL), mState(AiWander::Wander_ChooseAction), + mIsWanderingManually(false), + mCanWanderAlongPathGrid(true), mIdleAnimation(0), - mBadIdles() + mBadIdles(), + mPopulateAvailableNodes(true), + mAllowedNodes(), + mTrimCurrentNode(false), + mDoorCheckDuration(0), // TODO: maybe no longer needed + mStuckCount(0) {}; + + void setState(const AiWander::WanderState wanderState, const bool isManualWander = false) { + mState = wanderState; + mIsWanderingManually = isManualWander; + } }; AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): - mDistance(distance), mDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mRepeat(repeat) - , mStoredInitialActorPosition(false), mIsWanderDestReady(false) + mDistance(distance), mDuration(duration), mRemainingDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), + mRepeat(repeat), mStoredInitialActorPosition(false), mIsWanderDestReady(false) { mIdle.resize(8, 0); init(); @@ -95,11 +123,6 @@ namespace MWMechanics { // NOTE: mDistance and mDuration must be set already - - mStuckCount = 0;// TODO: maybe no longer needed - mDoorCheckDuration = 0; - mTrimCurrentNode = false; - mHasReturnPosition = false; mReturnPosition = osg::Vec3f(0,0,0); @@ -107,13 +130,6 @@ namespace MWMechanics mDistance = 0; if(mDuration < 0) mDuration = 0; - if(mDuration == 0) - mTimeOfDay = 0; - - mStartTime = MWBase::Environment::get().getWorld()->getTimeStamp(); - - mPopulateAvailableNodes = true; - } AiPackage * MWMechanics::AiWander::clone() const @@ -186,9 +202,11 @@ namespace MWMechanics if(!currentCell || cellChange) { currentCell = actor.getCell(); - mPopulateAvailableNodes = true; + storage.mPopulateAvailableNodes = true; } + mRemainingDuration -= ((duration*MWBase::Environment::get().getWorld()->getTimeScaleFactor()) / 3600); + cStats.setDrawState(DrawState_Nothing); cStats.setMovementFlag(CreatureStats::Flag_Run, false); @@ -203,29 +221,56 @@ namespace MWMechanics if (AI_REACTION_TIME <= lastReaction) { lastReaction = 0; - return reactionTimeActions(actor, storage, currentCell, cellChange, pos); + return reactionTimeActions(actor, storage, currentCell, cellChange, pos, duration); } else return false; } bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, - const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos) + const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos, float duration) { + if (mDistance <= 0) + storage.mCanWanderAlongPathGrid = false; + if (isPackageCompleted(actor, storage)) { + // Reset package so it can be used again + mRemainingDuration=mDuration; + init(); return true; } - // Initialization to discover & store allowed node points for this actor. - if (mPopulateAvailableNodes) + if (!mStoredInitialActorPosition) { - getAllowedNodes(actor, currentCell->getCell()); + mInitialActorPosition = actor.getRefData().getPosition().asVec3(); + mStoredInitialActorPosition = true; + } + + // Initialization to discover & store allowed node points for this actor. + if (storage.mPopulateAvailableNodes) + { + getAllowedNodes(actor, currentCell->getCell(), storage); } // Actor becomes stationary - see above URL's for previous research - if(mAllowedNodes.empty()) - mDistance = 0; + // If a creature or an NPC with a wander distance and no pathgrid is available, + // randomly idle or wander around near spawn point + if(storage.mAllowedNodes.empty() && mDistance > 0 && !storage.mIsWanderingManually) { + // Typically want to idle for a short time before the next wander + if (Misc::Rng::rollDice(100) >= 96) { + wanderNearStart(actor, storage, mDistance); + } else { + storage.setState(Wander_IdleNow); + } + } else if (storage.mAllowedNodes.empty() && !storage.mIsWanderingManually) { + storage.mCanWanderAlongPathGrid = false; + } + + // If Wandering manually and hit an obstacle, stop + if (storage.mIsWanderingManually && mObstacleCheck.check(actor, duration, 2.0f)) { + completeManualWalking(actor, storage); + } // Don't try to move if you are in a new cell (ie: positioncell command called) but still play idles. if(mDistance && cellChange) @@ -247,39 +292,38 @@ namespace MWMechanics playGreetingIfPlayerGetsTooClose(actor, storage); } - if ((wanderState == Wander_MoveNow) && mDistance) + if ((wanderState == Wander_MoveNow) && storage.mCanWanderAlongPathGrid) { // Construct a new path if there isn't one if(!mPathFinder.isPathConstructed()) { - if (mAllowedNodes.size()) + if (!storage.mAllowedNodes.empty()) { setPathToAnAllowedNode(actor, storage, pos); } } + } else if (storage.mIsWanderingManually && mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE)) { + completeManualWalking(actor, storage); } return false; // AiWander package not yet completed } + bool AiWander::getRepeat() const + { + return mRepeat; + } + + bool AiWander::isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage) { if (mDuration) { - // End package if duration is complete or mid-night hits: - MWWorld::TimeStamp currentTime = MWBase::Environment::get().getWorld()->getTimeStamp(); - if ((currentTime.getHour() >= mStartTime.getHour() + mDuration) || - (int(currentTime.getHour()) == 0 && currentTime.getDay() != mStartTime.getDay())) + // End package if duration is complete + if (mRemainingDuration <= 0) { - if (!mRepeat) - { stopWalking(actor, storage); return true; - } - else - { - mStartTime = currentTime; - } } } // if get here, not yet completed @@ -301,11 +345,73 @@ namespace MWMechanics if (mPathFinder.isPathConstructed()) { mIsWanderDestReady = true; - storage.mState = Wander_Walking; + storage.setState(Wander_Walking); } } } + /* + * Commands actor to walk to a random location near original spawn location. + */ + void AiWander::wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance) { + const ESM::Pathgrid::Point currentPosition = actor.getRefData().getPosition().pos; + const osg::Vec3f currentPositionVec3f = osg::Vec3f(currentPosition.mX, currentPosition.mY, currentPosition.mZ); + + std::size_t attempts = 10; // If a unit can't wander out of water, don't want to hang here + osg::Vec3f destination; + ESM::Pathgrid::Point destinationPosition; + bool isWaterCreature = actor.getClass().isPureWaterCreature(actor); + do { + // Determine a random location within radius of original position + const float pi = 3.14159265359f; + const float wanderRadius = Misc::Rng::rollClosedProbability() * wanderDistance; + const float randomDirection = Misc::Rng::rollClosedProbability() * 2.0f * pi; + const float destinationX = mInitialActorPosition.x() + wanderRadius * std::cos(randomDirection); + const float destinationY = mInitialActorPosition.y() + wanderRadius * std::sin(randomDirection); + const float destinationZ = mInitialActorPosition.z(); + destinationPosition = ESM::Pathgrid::Point(destinationX, destinationY, destinationZ); + destination = osg::Vec3f(destinationX, destinationY, destinationZ); + + // Check if land creature will walk onto water or if water creature will swim onto land + if ((!isWaterCreature && !destinationIsAtWater(actor, destination)) || + (isWaterCreature && !destinationThroughGround(currentPositionVec3f, destination))) { + mPathFinder.buildSyncedPath(currentPosition, destinationPosition, actor.getCell()); + mPathFinder.addPointToPath(destinationPosition); + + if (mPathFinder.isPathConstructed()) + { + mIsWanderDestReady = true; + storage.setState(Wander_Walking, true); + } + return; + } + } while (--attempts); + } + + /* + * Returns true if the position provided is above water. + */ + bool AiWander::destinationIsAtWater(const MWWorld::Ptr &actor, const osg::Vec3f& destination) { + float heightToGroundOrWater = MWBase::Environment::get().getWorld()->getDistToNearestRayHit(destination, osg::Vec3f(0,0,-1), 1000.0, true); + osg::Vec3f positionBelowSurface = destination; + positionBelowSurface[2] = positionBelowSurface[2] - heightToGroundOrWater - 1.0f; + return MWBase::Environment::get().getWorld()->isUnderwater(actor.getCell(), positionBelowSurface); + } + + /* + * Returns true if the start to end point travels through a collision point (land). + */ + bool AiWander::destinationThroughGround(const osg::Vec3f& startPoint, const osg::Vec3f& destination) { + return MWBase::Environment::get().getWorld()->castRay(startPoint.x(), startPoint.y(), startPoint.z(), + destination.x(), destination.y(), destination.z()); + } + + void AiWander::completeManualWalking(const MWWorld::Ptr &actor, AiWanderStorage &storage) { + stopWalking(actor, storage); + mObstacleCheck.clear(); + storage.setState(Wander_IdleNow); + } + void AiWander::doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos) { switch (storage.mState) @@ -335,15 +441,15 @@ namespace MWMechanics void AiWander::onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage) { // Check if an idle actor is too close to a door - if so start walking - mDoorCheckDuration += duration; - if (mDoorCheckDuration >= DOOR_CHECK_INTERVAL) + storage.mDoorCheckDuration += duration; + if (storage.mDoorCheckDuration >= DOOR_CHECK_INTERVAL) { - mDoorCheckDuration = 0; // restart timer + storage.mDoorCheckDuration = 0; // restart timer if (mDistance && // actor is not intended to be stationary proximityToDoor(actor, MIN_DIST_TO_DOOR_SQUARED*1.6f*1.6f)) // NOTE: checks interior cells only { - storage.mState = Wander_MoveNow; - mTrimCurrentNode = false; // just in case + storage.setState(Wander_MoveNow); + storage.mTrimCurrentNode = false; // just in case return; } } @@ -362,7 +468,7 @@ namespace MWMechanics GreetingState& greetingState = storage.mSaidGreeting; if (!checkIdle(actor, storage.mIdleAnimation) && (greetingState == Greet_Done || greetingState == Greet_None)) { - storage.mState = Wander_ChooseAction; + storage.setState(Wander_ChooseAction); } } @@ -373,7 +479,7 @@ namespace MWMechanics if (mIsWanderDestReady && pathTo(actor, mPathFinder.getPath().back(), duration, DESTINATION_TOLERANCE)) { stopWalking(actor, storage); - storage.mState = Wander_ChooseAction; + storage.setState(Wander_ChooseAction); mHasReturnPosition = false; } else @@ -391,7 +497,7 @@ namespace MWMechanics if (!idleAnimation && mDistance) { - storage.mState = Wander_MoveNow; + storage.setState(Wander_MoveNow); return; } if(idleAnimation) @@ -401,14 +507,13 @@ namespace MWMechanics if(!playIdle(actor, idleAnimation)) { storage.mBadIdles.push_back(idleAnimation); - storage.mState = Wander_ChooseAction; + storage.setState(Wander_ChooseAction); return; } } } - // Recreate vanilla (broken?) behavior of resetting start time of AIWander: - mStartTime = MWBase::Environment::get().getWorld()->getTimeStamp(); - storage.mState = Wander_IdleNow; + + storage.setState(Wander_IdleNow); } void AiWander::evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage, float duration, ESM::Position& pos) @@ -419,25 +524,25 @@ namespace MWMechanics if (proximityToDoor(actor)) // NOTE: checks interior cells only { // remove allowed points then select another random destination - mTrimCurrentNode = true; - trimAllowedNodes(mAllowedNodes, mPathFinder); + storage.mTrimCurrentNode = true; + trimAllowedNodes(storage.mAllowedNodes, mPathFinder); mObstacleCheck.clear(); mPathFinder.clearPath(); - storage.mState = Wander_MoveNow; + storage.setState(Wander_MoveNow); } - mStuckCount++; // TODO: maybe no longer needed + storage.mStuckCount++; // TODO: maybe no longer needed } // if stuck for sufficiently long, act like current location was the destination - if (mStuckCount >= COUNT_BEFORE_RESET) // something has gone wrong, reset + if (storage.mStuckCount >= COUNT_BEFORE_RESET) // something has gone wrong, reset { //std::cout << "Reset \""<< cls.getName(actor) << "\"" << std::endl; mObstacleCheck.clear(); stopWalking(actor, storage); - storage.mState = Wander_ChooseAction; - mStuckCount = 0; + storage.setState(Wander_ChooseAction); + storage.mStuckCount = 0; } } @@ -511,7 +616,7 @@ namespace MWMechanics { stopWalking(actor, storage); mObstacleCheck.clear(); - storage.mState = Wander_IdleNow; + storage.setState(Wander_IdleNow); } turnActorToFacePlayer(actorPos, playerPos, storage); @@ -542,8 +647,8 @@ namespace MWMechanics void AiWander::setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos) { - unsigned int randNode = Misc::Rng::rollDice(mAllowedNodes.size()); - ESM::Pathgrid::Point dest(mAllowedNodes[randNode]); + unsigned int randNode = Misc::Rng::rollDice(storage.mAllowedNodes.size()); + ESM::Pathgrid::Point dest(storage.mAllowedNodes[randNode]); ToWorldCoordinates(dest, storage.mCell->getCell()); // actor position is already in world co-ordinates @@ -557,20 +662,20 @@ namespace MWMechanics mIsWanderDestReady = true; // Remove this node as an option and add back the previously used node (stops NPC from picking the same node): - ESM::Pathgrid::Point temp = mAllowedNodes[randNode]; - mAllowedNodes.erase(mAllowedNodes.begin() + randNode); + ESM::Pathgrid::Point temp = storage.mAllowedNodes[randNode]; + storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + randNode); // check if mCurrentNode was taken out of mAllowedNodes - if (mTrimCurrentNode && mAllowedNodes.size() > 1) - mTrimCurrentNode = false; + if (storage.mTrimCurrentNode && storage.mAllowedNodes.size() > 1) + storage.mTrimCurrentNode = false; else - mAllowedNodes.push_back(mCurrentNode); - mCurrentNode = temp; + storage.mAllowedNodes.push_back(storage.mCurrentNode); + storage.mCurrentNode = temp; - storage.mState = Wander_Walking; + storage.setState(Wander_Walking); } // Choose a different node and delete this one from possible nodes because it is uncreachable: else - mAllowedNodes.erase(mAllowedNodes.begin() + randNode); + storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + randNode); } void AiWander::ToWorldCoordinates(ESM::Pathgrid::Point& point, const ESM::Cell * cell) @@ -675,20 +780,22 @@ namespace MWMechanics void AiWander::fastForward(const MWWorld::Ptr& actor, AiState &state) { + // Update duration counter + mRemainingDuration--; if (mDistance == 0) return; - if (mPopulateAvailableNodes) - getAllowedNodes(actor, actor.getCell()->getCell()); + AiWanderStorage& storage = state.get(); + if (storage.mPopulateAvailableNodes) + getAllowedNodes(actor, actor.getCell()->getCell(), storage); - if (mAllowedNodes.empty()) + if (storage.mAllowedNodes.empty()) return; + int index = Misc::Rng::rollDice(storage.mAllowedNodes.size()); + ESM::Pathgrid::Point dest = storage.mAllowedNodes[index]; state.moveIn(new AiWanderStorage()); - int index = Misc::Rng::rollDice(mAllowedNodes.size()); - ESM::Pathgrid::Point dest = mAllowedNodes[index]; - dest.mX += OffsetToPreventOvercrowding(); dest.mY += OffsetToPreventOvercrowding(); ToWorldCoordinates(dest, actor.getCell()->getCell()); @@ -698,86 +805,88 @@ namespace MWMechanics actor.getClass().adjustPosition(actor, false); // may have changed cell - mPopulateAvailableNodes = true; + storage.mPopulateAvailableNodes = true; } int AiWander::OffsetToPreventOvercrowding() { - return static_cast(DESTINATION_TOLERANCE * (Misc::Rng::rollProbability() * 2.0f - 1.0f)); + return static_cast(20 * (Misc::Rng::rollProbability() * 2.0f - 1.0f)); } - void AiWander::getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell) + void AiWander::getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell, AiWanderStorage& storage) { - if (!mStoredInitialActorPosition) - { - mInitialActorPosition = actor.getRefData().getPosition().asVec3(); - mStoredInitialActorPosition = true; - } - // infrequently used, therefore no benefit in caching it as a member const ESM::Pathgrid * pathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*cell); + const MWWorld::CellStore* cellStore = actor.getCell(); + + storage.mAllowedNodes.clear(); // If there is no path this actor doesn't go anywhere. See: // https://forum.openmw.org/viewtopic.php?t=1556 // http://www.fliggerty.com/phpBB3/viewtopic.php?f=30&t=5833 // Note: In order to wander, need at least two points. if(!pathgrid || (pathgrid->mPoints.size() < 2)) - mDistance = 0; + storage.mCanWanderAlongPathGrid = false; // A distance value passed into the constructor indicates how far the // actor can wander from the spawn position. AiWander assumes that // pathgrid points are available, and uses them to randomly select wander // destinations within the allowed set of pathgrid points (nodes). // ... pathgrids don't usually include water, so swimmers ignore them - if (mDistance && !actor.getClass().isPureWaterCreature(actor)) + if (mDistance && storage.mCanWanderAlongPathGrid && !actor.getClass().isPureWaterCreature(actor)) { // get NPC's position in local (i.e. cell) co-ordinates osg::Vec3f npcPos(mInitialActorPosition); CoordinateConverter(cell).toLocal(npcPos); + + // Find closest pathgrid point + int closestPointIndex = PathFinder::GetClosestPoint(pathgrid, npcPos); // mAllowedNodes for this actor with pathgrid point indexes based on mDistance + // and if the point is connected to the closest current point // NOTE: mPoints and mAllowedNodes are in local co-ordinates int pointIndex = 0; for(unsigned int counter = 0; counter < pathgrid->mPoints.size(); counter++) { osg::Vec3f nodePos(PathFinder::MakeOsgVec3(pathgrid->mPoints[counter])); - if((npcPos - nodePos).length2() <= mDistance * mDistance) + if((npcPos - nodePos).length2() <= mDistance * mDistance && + cellStore->isPointConnected(closestPointIndex, counter)) { - mAllowedNodes.push_back(pathgrid->mPoints[counter]); + storage.mAllowedNodes.push_back(pathgrid->mPoints[counter]); pointIndex = counter; } } - if (mAllowedNodes.size() == 1) + if (storage.mAllowedNodes.size() == 1) { - AddNonPathGridAllowedPoints(npcPos, pathgrid, pointIndex); + AddNonPathGridAllowedPoints(npcPos, pathgrid, pointIndex, storage); } - if(!mAllowedNodes.empty()) + if(!storage.mAllowedNodes.empty()) { - SetCurrentNodeToClosestAllowedNode(npcPos); + SetCurrentNodeToClosestAllowedNode(npcPos, storage); } } - mPopulateAvailableNodes = false; + storage.mPopulateAvailableNodes = false; } // When only one path grid point in wander distance, // additional points for NPC to wander to are: // 1. NPC's initial location // 2. Partway along the path between the point and its connected points. - void AiWander::AddNonPathGridAllowedPoints(osg::Vec3f npcPos, const ESM::Pathgrid * pathGrid, int pointIndex) + void AiWander::AddNonPathGridAllowedPoints(osg::Vec3f npcPos, const ESM::Pathgrid * pathGrid, int pointIndex, AiWanderStorage& storage) { - mAllowedNodes.push_back(PathFinder::MakePathgridPoint(npcPos)); + storage.mAllowedNodes.push_back(PathFinder::MakePathgridPoint(npcPos)); for (std::vector::const_iterator it = pathGrid->mEdges.begin(); it != pathGrid->mEdges.end(); ++it) { if (it->mV0 == pointIndex) { - AddPointBetweenPathGridPoints(pathGrid->mPoints[it->mV0], pathGrid->mPoints[it->mV1]); + AddPointBetweenPathGridPoints(pathGrid->mPoints[it->mV0], pathGrid->mPoints[it->mV1], storage); } } } - void AiWander::AddPointBetweenPathGridPoints(const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end) + void AiWander::AddPointBetweenPathGridPoints(const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage) { osg::Vec3f vectorStart = PathFinder::MakeOsgVec3(start); osg::Vec3f delta = PathFinder::MakeOsgVec3(end) - vectorStart; @@ -789,16 +898,16 @@ namespace MWMechanics // must not travel longer than distance between waypoints or NPC goes past waypoint distance = std::min(distance, static_cast(length)); delta *= distance; - mAllowedNodes.push_back(PathFinder::MakePathgridPoint(vectorStart + delta)); + storage.mAllowedNodes.push_back(PathFinder::MakePathgridPoint(vectorStart + delta)); } - void AiWander::SetCurrentNodeToClosestAllowedNode(osg::Vec3f npcPos) + void AiWander::SetCurrentNodeToClosestAllowedNode(osg::Vec3f npcPos, AiWanderStorage& storage) { float distanceToClosestNode = std::numeric_limits::max(); unsigned int index = 0; - for (unsigned int counterThree = 0; counterThree < mAllowedNodes.size(); counterThree++) + for (unsigned int counterThree = 0; counterThree < storage.mAllowedNodes.size(); counterThree++) { - osg::Vec3f nodePos(PathFinder::MakeOsgVec3(mAllowedNodes[counterThree])); + osg::Vec3f nodePos(PathFinder::MakeOsgVec3(storage.mAllowedNodes[counterThree])); float tempDist = (npcPos - nodePos).length2(); if (tempDist < distanceToClosestNode) { @@ -806,17 +915,23 @@ namespace MWMechanics distanceToClosestNode = tempDist; } } - mCurrentNode = mAllowedNodes[index]; - mAllowedNodes.erase(mAllowedNodes.begin() + index); + storage.mCurrentNode = storage.mAllowedNodes[index]; + storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + index); } void AiWander::writeState(ESM::AiSequence::AiSequence &sequence) const { + float remainingDuration; + if (mRemainingDuration > 0 && mRemainingDuration < 24) + remainingDuration = mRemainingDuration; + else + remainingDuration = mDuration; + std::auto_ptr wander(new ESM::AiSequence::AiWander()); wander->mData.mDistance = mDistance; wander->mData.mDuration = mDuration; wander->mData.mTimeOfDay = mTimeOfDay; - wander->mStartTime = mStartTime.toEsm(); + wander->mDurationData.mRemainingDuration = remainingDuration; assert (mIdle.size() == 8); for (int i=0; i<8; ++i) wander->mData.mIdle[i] = mIdle[i]; @@ -834,16 +949,19 @@ namespace MWMechanics AiWander::AiWander (const ESM::AiSequence::AiWander* wander) : mDistance(wander->mData.mDistance) , mDuration(wander->mData.mDuration) + , mRemainingDuration(wander->mDurationData.mRemainingDuration) , mTimeOfDay(wander->mData.mTimeOfDay) , mRepeat(wander->mData.mShouldRepeat != 0) , mStoredInitialActorPosition(wander->mStoredInitialActorPosition) - , mStartTime(MWWorld::TimeStamp(wander->mStartTime)) , mIsWanderDestReady(false) { if (mStoredInitialActorPosition) mInitialActorPosition = wander->mInitialActorPosition; for (int i=0; i<8; ++i) mIdle.push_back(wander->mData.mIdle[i]); + if (mRemainingDuration <= 0 || mRemainingDuration >= 24) + mRemainingDuration = mDuration; + init(); } diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index eacadbcfdb..a46b3cbad4 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -5,12 +5,10 @@ #include -#include "pathfinding.hpp" -#include "obstacle.hpp" - #include "../mwworld/timestamp.hpp" - +#include "pathfinding.hpp" +#include "obstacle.hpp" #include "aistate.hpp" namespace ESM @@ -23,9 +21,7 @@ namespace ESM } namespace MWMechanics -{ - - +{ struct AiWanderStorage; /// \brief Causes the Actor to wander within a specified range @@ -35,15 +31,13 @@ namespace MWMechanics /// Constructor /** \param distance Max distance the ACtor will wander \param duration Time, in hours, that this package will be preformed - \param timeOfDay Start time of the package, if it has a duration. Currently unimplemented + \param timeOfDay Currently unimplemented. Not functional in the original engine. \param idle Chances of each idle to play (9 in total) \param repeat Repeat wander or not **/ AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat); AiWander (const ESM::AiSequence::AiWander* wander); - - virtual AiPackage *clone() const; virtual bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration); @@ -58,6 +52,8 @@ namespace MWMechanics virtual void fastForward(const MWWorld::Ptr& actor, AiState& state); + bool getRepeat() const; + enum GreetingState { Greet_None, Greet_InProgress, @@ -70,10 +66,10 @@ namespace MWMechanics Wander_MoveNow, Wander_Walking }; + private: // NOTE: mDistance and mDuration must be set already void init(); - void stopWalking(const MWWorld::Ptr& actor, AiWanderStorage& storage); /// Have the given actor play an idle animation @@ -91,49 +87,33 @@ namespace MWMechanics void onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos); void onChooseActionStatePerFrameActions(const MWWorld::Ptr& actor, AiWanderStorage& storage); bool reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, - const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos); + const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos, float duration); bool isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage); void returnToStartLocation(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos); + void wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance); + bool destinationIsAtWater(const MWWorld::Ptr &actor, const osg::Vec3f& destination); + bool destinationThroughGround(const osg::Vec3f& startPoint, const osg::Vec3f& destination); + void completeManualWalking(const MWWorld::Ptr &actor, AiWanderStorage &storage); int mDistance; // how far the actor can wander from the spawn point int mDuration; + float mRemainingDuration; int mTimeOfDay; std::vector mIdle; bool mRepeat; - bool mHasReturnPosition; // NOTE: Could be removed if mReturnPosition was initialized to actor position, // if we had the actor in the AiWander constructor... osg::Vec3f mReturnPosition; - osg::Vec3f mInitialActorPosition; bool mStoredInitialActorPosition; - - - // do we need to calculate allowed nodes based on mDistance - bool mPopulateAvailableNodes; - - - - - MWWorld::TimeStamp mStartTime; - - // allowed pathgrid nodes based on mDistance from the spawn point - std::vector mAllowedNodes; - - void getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell); - - ESM::Pathgrid::Point mCurrentNode; - bool mTrimCurrentNode; - void trimAllowedNodes(std::vector& nodes, - const PathFinder& pathfinder); - - - float mDoorCheckDuration; - int mStuckCount; bool mIsWanderDestReady; + void getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell, AiWanderStorage& storage); + + void trimAllowedNodes(std::vector& nodes, const PathFinder& pathfinder); + // constants for converting idleSelect values into groupNames enum GroupIndex { @@ -144,19 +124,17 @@ namespace MWMechanics /// convert point from local (i.e. cell) to world co-ordinates void ToWorldCoordinates(ESM::Pathgrid::Point& point, const ESM::Cell * cell); - void SetCurrentNodeToClosestAllowedNode(osg::Vec3f npcPos); + void SetCurrentNodeToClosestAllowedNode(osg::Vec3f npcPos, AiWanderStorage& storage); - void AddNonPathGridAllowedPoints(osg::Vec3f npcPos, const ESM::Pathgrid * pathGrid, int pointIndex); + void AddNonPathGridAllowedPoints(osg::Vec3f npcPos, const ESM::Pathgrid * pathGrid, int pointIndex, AiWanderStorage& storage); - void AddPointBetweenPathGridPoints(const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end); + void AddPointBetweenPathGridPoints(const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage); /// lookup table for converting idleSelect value to groupName static const std::string sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1]; static int OffsetToPreventOvercrowding(); - }; - - + }; } #endif diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index d39c881506..322d592801 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -15,7 +15,6 @@ #include #include - #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -451,6 +450,16 @@ MWMechanics::Alchemy::TEffectsIterator MWMechanics::Alchemy::endEffects() const return mEffects.end(); } +bool MWMechanics::Alchemy::knownEffect(unsigned int potionEffectIndex, const MWWorld::Ptr &npc) +{ + MWMechanics::NpcStats& npcStats = npc.getClass().getNpcStats(npc); + int alchemySkill = npcStats.getSkill (ESM::Skill::Alchemy).getBase(); + static const float fWortChanceValue = + MWBase::Environment::get().getWorld()->getStore().get().find("fWortChanceValue")->getFloat(); + return (potionEffectIndex <= 1 && alchemySkill >= fWortChanceValue) + || (potionEffectIndex <= 3 && alchemySkill >= fWortChanceValue*2); +} + MWMechanics::Alchemy::Result MWMechanics::Alchemy::create (const std::string& name) { if (mTools[ESM::Apparatus::MortarPestle].isEmpty()) diff --git a/apps/openmw/mwmechanics/alchemy.hpp b/apps/openmw/mwmechanics/alchemy.hpp index caba26f149..f351881e0d 100644 --- a/apps/openmw/mwmechanics/alchemy.hpp +++ b/apps/openmw/mwmechanics/alchemy.hpp @@ -80,6 +80,9 @@ namespace MWMechanics public: + static bool knownEffect (unsigned int potionEffectIndex, const MWWorld::Ptr& npc); + ///< Does npc have sufficient alchemy skill to know about this potion effect? + void setAlchemist (const MWWorld::Ptr& npc); ///< Set alchemist and configure alchemy setup accordingly. \a npc may be empty to indicate that /// there is no alchemist (alchemy session has ended). diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index db1b82ba6e..0b1c96186c 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -21,13 +21,6 @@ #include - -#include "movement.hpp" -#include "npcstats.hpp" -#include "creaturestats.hpp" -#include "security.hpp" -#include "actorutil.hpp" - #include #include @@ -40,13 +33,18 @@ #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwbase/statemanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/player.hpp" +#include "movement.hpp" +#include "npcstats.hpp" +#include "creaturestats.hpp" +#include "security.hpp" +#include "actorutil.hpp" + namespace { @@ -227,7 +225,7 @@ public: { return weap.type == type; } }; -std::string CharacterController::chooseRandomGroup (const std::string& prefix, int* num) +std::string CharacterController::chooseRandomGroup (const std::string& prefix, int* num) const { int numAnims=0; while (mAnimation->hasAnimation(prefix + toString(numAnims+1))) @@ -239,88 +237,83 @@ std::string CharacterController::chooseRandomGroup (const std::string& prefix, i return prefix + toString(roll); } -void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force) +void CharacterController::refreshHitRecoilAnims() { - // hit recoils/knockdown animations handling - if(mPtr.getClass().isActor()) + bool recovery = mPtr.getClass().getCreatureStats(mPtr).getHitRecovery(); + bool knockdown = mPtr.getClass().getCreatureStats(mPtr).getKnockedDown(); + bool block = mPtr.getClass().getCreatureStats(mPtr).getBlock(); + if(mHitState == CharState_None) { - bool recovery = mPtr.getClass().getCreatureStats(mPtr).getHitRecovery(); - bool knockdown = mPtr.getClass().getCreatureStats(mPtr).getKnockedDown(); - bool block = mPtr.getClass().getCreatureStats(mPtr).getBlock(); - if(mHitState == CharState_None) + if ((mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0 + || mPtr.getClass().getCreatureStats(mPtr).getFatigue().getBase() == 0) + && mAnimation->hasAnimation("knockout")) { - if ((mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0 - || mPtr.getClass().getCreatureStats(mPtr).getFatigue().getBase() == 0) - && mAnimation->hasAnimation("knockout")) - { - mHitState = CharState_KnockOut; - mCurrentHit = "knockout"; - mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul); - mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true); - } - else if(knockdown && mAnimation->hasAnimation("knockdown")) - { - mHitState = CharState_KnockDown; - mCurrentHit = "knockdown"; - mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); - } - else if (recovery) - { - std::string anim = chooseRandomGroup("hit"); - if (mAnimation->hasAnimation(anim)) - { - mHitState = CharState_Hit; - mCurrentHit = anim; - mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); - } - } - else if (block && mAnimation->hasAnimation("shield")) - { - mHitState = CharState_Block; - mCurrentHit = "shield"; - MWRender::Animation::AnimPriority priorityBlock (Priority_Hit); - priorityBlock[MWRender::Animation::BoneGroup_LeftArm] = Priority_Block; - mAnimation->play(mCurrentHit, priorityBlock, MWRender::Animation::BlendMask_All, true, 1, "block start", "block stop", 0.0f, 0); - } - - // Cancel upper body animations - if (mHitState == CharState_KnockDown || mHitState == CharState_KnockOut) - { - if (mUpperBodyState > UpperCharState_WeapEquiped) - { - mAnimation->disable(mCurrentWeapon); - mUpperBodyState = UpperCharState_WeapEquiped; - } - else if (mUpperBodyState > UpperCharState_Nothing && mUpperBodyState < UpperCharState_WeapEquiped) - { - mAnimation->disable(mCurrentWeapon); - mUpperBodyState = UpperCharState_Nothing; - } - } + mHitState = CharState_KnockOut; + mCurrentHit = "knockout"; + mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul); + mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true); } - else if(!mAnimation->isPlaying(mCurrentHit)) - { - mCurrentHit.erase(); - if (knockdown) - mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(false); - if (recovery) - mPtr.getClass().getCreatureStats(mPtr).setHitRecovery(false); - if (block) - mPtr.getClass().getCreatureStats(mPtr).setBlock(false); - mHitState = CharState_None; - } - else if (mHitState == CharState_KnockOut && mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() > 0) + else if(knockdown && mAnimation->hasAnimation("knockdown")) { mHitState = CharState_KnockDown; - mAnimation->disable(mCurrentHit); - mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "loop stop", "stop", 0.0f, 0); + mCurrentHit = "knockdown"; + mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); + } + else if (recovery) + { + std::string anim = chooseRandomGroup("hit"); + if (mAnimation->hasAnimation(anim)) + { + mHitState = CharState_Hit; + mCurrentHit = anim; + mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); + } + } + else if (block && mAnimation->hasAnimation("shield")) + { + mHitState = CharState_Block; + mCurrentHit = "shield"; + MWRender::Animation::AnimPriority priorityBlock (Priority_Hit); + priorityBlock[MWRender::Animation::BoneGroup_LeftArm] = Priority_Block; + mAnimation->play(mCurrentHit, priorityBlock, MWRender::Animation::BlendMask_All, true, 1, "block start", "block stop", 0.0f, 0); + } + + // Cancel upper body animations + if (mHitState == CharState_KnockDown || mHitState == CharState_KnockOut) + { + if (mUpperBodyState > UpperCharState_WeapEquiped) + { + mAnimation->disable(mCurrentWeapon); + mUpperBodyState = UpperCharState_WeapEquiped; + } + else if (mUpperBodyState > UpperCharState_Nothing && mUpperBodyState < UpperCharState_WeapEquiped) + { + mAnimation->disable(mCurrentWeapon); + mUpperBodyState = UpperCharState_Nothing; + } } } + else if(!mAnimation->isPlaying(mCurrentHit)) + { + mCurrentHit.erase(); + if (knockdown) + mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(false); + if (recovery) + mPtr.getClass().getCreatureStats(mPtr).setHitRecovery(false); + if (block) + mPtr.getClass().getCreatureStats(mPtr).setBlock(false); + mHitState = CharState_None; + } + else if (mHitState == CharState_KnockOut && mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() > 0) + { + mHitState = CharState_KnockDown; + mAnimation->disable(mCurrentHit); + mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "loop stop", "stop", 0.0f, 0); + } +} - const WeaponInfo *weap = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(mWeaponType)); - if (!mPtr.getClass().isBipedal(mPtr)) - weap = sWeaponTypeListEnd; - +void CharacterController::refreshJumpAnims(const WeaponInfo* weap, JumpingState jump, bool force) +{ if(force || jump != mJumpState) { bool startAtLoop = (jump == mJumpState); @@ -359,7 +352,10 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat 1.0f, "loop stop", "stop", 0.0f, 0); } } +} +void CharacterController::refreshMovementAnims(const WeaponInfo* weap, CharacterState movement, bool force) +{ if(force || movement != mMovementState) { mMovementState = movement; @@ -467,16 +463,10 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat 1.f, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul); } } +} - // idle handled last as it can depend on the other states - // 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 - || (mMovementState != CharState_None && mMovementState != CharState_TurnLeft && mMovementState != CharState_TurnRight) - || mHitState != CharState_None) - && !mPtr.getClass().isBipedal(mPtr)) - idle = CharState_None; - +void CharacterController::refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force) +{ if(force || idle != mIdleState) { mIdleState = idle; @@ -514,6 +504,30 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat } } +void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force) +{ + if (mPtr.getClass().isActor()) + refreshHitRecoilAnims(); + + const WeaponInfo *weap = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(mWeaponType)); + if (!mPtr.getClass().isBipedal(mPtr)) + weap = sWeaponTypeListEnd; + + refreshJumpAnims(weap, jump, force); + refreshMovementAnims(weap, movement, force); + + // idle handled last as it can depend on the other states + // 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 + || (mMovementState != CharState_None && mMovementState != CharState_TurnLeft && mMovementState != CharState_TurnRight) + || mHitState != CharState_None) + && !mPtr.getClass().isBipedal(mPtr)) + idle = CharState_None; + + refreshIdleAnims(weap, idle, force); +} + void getWeaponGroup(WeaponType weaptype, std::string &group) { @@ -629,6 +643,13 @@ void CharacterController::playDeath(float startpoint, CharacterState death) false, 1.0f, "start", "stop", startpoint, 0); } +CharacterState CharacterController::chooseRandomDeathState() const +{ + int selected=0; + chooseRandomGroup("death", &selected); + return static_cast(CharState_Death1 + (selected-1)); +} + void CharacterController::playRandomDeath(float startpoint) { if (mPtr == getPlayer()) @@ -652,9 +673,7 @@ void CharacterController::playRandomDeath(float startpoint) } else { - int selected=0; - chooseRandomGroup("death", &selected); - mDeathState = static_cast(CharState_Death1 + (selected-1)); + mDeathState = chooseRandomDeathState(); } playDeath(startpoint, mDeathState); } @@ -668,6 +687,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim , mHasMovedInXY(false) , mMovementAnimationControlled(true) , mDeathState(CharState_None) + , mFloatToSurface(true) , mHitState(CharState_None) , mUpperBodyState(UpperCharState_Nothing) , mJumpState(JumpState_None) @@ -713,9 +733,20 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim mIdleState = CharState_Idle; else { - // Set the death state, but don't play it yet - // We will play it in the first frame, but only if no script set the skipAnim flag - mDeathState = static_cast(CharState_Death1 + mPtr.getClass().getCreatureStats(mPtr).getDeathAnimation()); + const MWMechanics::CreatureStats& cStats = mPtr.getClass().getCreatureStats(mPtr); + if (cStats.isDeathAnimationFinished()) + { + // Set the death state, but don't play it yet + // We will play it in the first frame, but only if no script set the skipAnim flag + signed char deathanim = cStats.getDeathAnimation(); + if (deathanim == -1) + mDeathState = chooseRandomDeathState(); + else + mDeathState = static_cast(CharState_Death1 + deathanim); + + mFloatToSurface = false; + } + // else: nothing to do, will detect death in the next frame and start playing death animation } } else @@ -754,6 +785,7 @@ void CharacterController::handleTextKey(const std::string &groupname, const std: if(evt.compare(0, 7, "sound: ") == 0) { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + sndMgr->stopSound3D(mPtr, evt.substr(7)); sndMgr->playSound3D(mPtr, evt.substr(7), 1.0f, 1.0f); return; } @@ -794,7 +826,10 @@ void CharacterController::handleTextKey(const std::string &groupname, const std: MWBase::SoundManager::Play_NoPlayerLocal); } else + { + sndMgr->stopSound3D(mPtr, sound); sndMgr->playSound3D(mPtr, sound, volume, pitch); + } } return; } @@ -1279,14 +1314,19 @@ bool CharacterController::updateWeaponState() mAttackType = "shoot"; else { - if(isWeapon && mPtr == getPlayer() && - Settings::Manager::getBool("best attack", "Game")) + if (isWeapon) { - MWWorld::ContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - mAttackType = getBestAttack(weapon->get()->mBase); + if(mPtr == getPlayer() && + Settings::Manager::getBool("best attack", "Game")) + { + MWWorld::ContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + mAttackType = getBestAttack(weapon->get()->mBase); + } + else + setAttackTypeBasedOnMovement(); } else - determineAttackType(); + setAttackTypeRandomly(); } mAnimation->play(mCurrentWeapon, priorityWeapon, @@ -1864,7 +1904,7 @@ void CharacterController::update(float duration) { playDeath(1.f, mDeathState); } - + // We must always queue movement, even if there is none, to apply gravity. world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); } @@ -1897,6 +1937,9 @@ void CharacterController::update(float duration) if (mSkipAnim) mAnimation->updateEffects(duration); + if (mFloatToSurface && cls.isActor() && cls.getCreatureStats(mPtr).isDead() && cls.canSwim(mPtr)) + moved.z() = 1.0; + // Update movement if(mMovementAnimationControlled && mPtr.getClass().isActor()) world->queueMovement(mPtr, moved); @@ -1976,30 +2019,28 @@ void CharacterController::forceStateUpdate() mAnimation->runAnimation(0.f); } -bool CharacterController::kill() +CharacterController::KillResult CharacterController::kill() { - if( isDead() ) + if (mDeathState == CharState_None) { - if( mPtr == getPlayer() && !isAnimPlaying(mCurrentDeath) ) - { - //player's death animation is over - MWBase::Environment::get().getStateManager()->askLoadRecent(); - } - return false; + playRandomDeath(); + + mAnimation->disable(mCurrentIdle); + + mIdleState = CharState_None; + mCurrentIdle.clear(); + return Result_DeathAnimStarted; } - playRandomDeath(); - - mAnimation->disable(mCurrentIdle); - - mIdleState = CharState_None; - mCurrentIdle.clear(); - - // Play Death Music if it was the player dying - if(mPtr == getPlayer()) - MWBase::Environment::get().getSoundManager()->streamMusic("Special/MW_Death.mp3"); - - return true; + MWMechanics::CreatureStats& cStats = mPtr.getClass().getCreatureStats(mPtr); + if (isAnimPlaying(mCurrentDeath)) + return Result_DeathAnimPlaying; + if (!cStats.isDeathAnimationFinished()) + { + cStats.setDeathAnimationFinished(true); + return Result_DeathAnimJustFinished; + } + return Result_DeathAnimFinished; } void CharacterController::resurrect() @@ -2056,7 +2097,18 @@ void CharacterController::updateMagicEffects() mAnimation->setLightEffect(light); } -void CharacterController::determineAttackType() +void CharacterController::setAttackTypeRandomly() +{ + float random = Misc::Rng::rollProbability(); + if (random >= 2/3.f) + mAttackType = "thrust"; + else if (random >= 1/3.f) + mAttackType = "slash"; + else + mAttackType = "chop"; +} + +void CharacterController::setAttackTypeBasedOnMovement() { float *move = mPtr.getClass().getMovementSettings(mPtr).mPosition; @@ -2135,10 +2187,10 @@ void CharacterController::updateHeadTracking(float duration) if (!mHeadTrackTarget.isEmpty()) { - osg::MatrixList mats = head->getWorldMatrices(); - if (mats.empty()) + osg::NodePathList nodepaths = head->getParentalNodePaths(); + if (nodepaths.empty()) return; - osg::Matrixf mat = mats[0]; + osg::Matrixf mat = osg::computeLocalToWorld(nodepaths[0]); osg::Vec3f headPos = mat.getTrans(); osg::Vec3f direction; @@ -2149,9 +2201,9 @@ void CharacterController::updateHeadTracking(float duration) node = anim->getNode("Bip01 Head"); if (node != NULL) { - osg::MatrixList mats = node->getWorldMatrices(); - if (mats.size()) - direction = mats[0].getTrans() - headPos; + osg::NodePathList nodepaths = node->getParentalNodePaths(); + if (!nodepaths.empty()) + direction = osg::computeLocalToWorld(nodepaths[0]).getTrans() - headPos; } else // no head node to look at, fall back to look at center of collision box diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 5e43bc2b73..b7e5feb6ff 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -140,6 +140,8 @@ enum JumpingState { JumpState_Landing }; +struct WeaponInfo; + class CharacterController : public MWRender::Animation::TextKeyListener { MWWorld::Ptr mPtr; @@ -160,6 +162,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener CharacterState mDeathState; std::string mCurrentDeath; + bool mFloatToSurface; CharacterState mHitState; std::string mCurrentHit; @@ -188,9 +191,14 @@ class CharacterController : public MWRender::Animation::TextKeyListener bool mAttackingOrSpell; - void determineAttackType(); + void setAttackTypeBasedOnMovement(); + void setAttackTypeRandomly(); void refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force=false); + void refreshHitRecoilAnims(); + void refreshJumpAnims(const WeaponInfo* weap, JumpingState jump, bool force=false); + void refreshMovementAnims(const WeaponInfo* weap, CharacterState movement, bool force=false); + void refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force=false); void clearAnimQueue(); @@ -205,11 +213,12 @@ class CharacterController : public MWRender::Animation::TextKeyListener void updateMagicEffects(); void playDeath(float startpoint, CharacterState death); + CharacterState chooseRandomDeathState() const; void playRandomDeath(float startpoint = 0.0f); /// choose a random animation group with \a prefix and numeric suffix /// @param num if non-NULL, the chosen animation number will be written here - std::string chooseRandomGroup (const std::string& prefix, int* num = NULL); + std::string chooseRandomGroup (const std::string& prefix, int* num = NULL) const; bool updateCarriedLeftVisible(WeaponType weaptype) const; @@ -231,8 +240,14 @@ public: void skipAnim(); bool isAnimPlaying(const std::string &groupName); - /// @return false if the character has already been killed before - bool kill(); + enum KillResult + { + Result_DeathAnimStarted, + Result_DeathAnimPlaying, + Result_DeathAnimJustFinished, + Result_DeathAnimFinished + }; + KillResult kill(); void resurrect(); bool isDead() const diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index a01dc7079c..df2793bcb7 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -8,17 +8,16 @@ #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/soundmanager.hpp" - -#include "../mwmechanics/npcstats.hpp" -#include "../mwmechanics/movement.hpp" -#include "../mwmechanics/spellcasting.hpp" -#include "../mwmechanics/difficultyscaling.hpp" +#include "../mwbase/windowmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwbase/windowmanager.hpp" +#include "npcstats.hpp" +#include "movement.hpp" +#include "spellcasting.hpp" +#include "difficultyscaling.hpp" #include "actorutil.hpp" namespace @@ -29,29 +28,29 @@ float signedAngleRadians (const osg::Vec3f& v1, const osg::Vec3f& v2, const osg: return std::atan2((normal * (v1 ^ v2)), (v1 * v2)); } -bool applyEnchantment (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& object, const osg::Vec3f& hitPosition) -{ - std::string enchantmentName = !object.isEmpty() ? object.getClass().getEnchantment(object) : ""; - if (!enchantmentName.empty()) - { - const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( - enchantmentName); - if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) - { - MWMechanics::CastSpell cast(attacker, victim); - cast.mHitPosition = hitPosition; - cast.cast(object); - return true; - } - } - return false; -} - } namespace MWMechanics { + bool applyOnStrikeEnchantment (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& object, const osg::Vec3f& hitPosition) + { + std::string enchantmentName = !object.isEmpty() ? object.getClass().getEnchantment(object) : ""; + if (!enchantmentName.empty()) + { + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( + enchantmentName); + if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) + { + MWMechanics::CastSpell cast(attacker, victim); + cast.mHitPosition = hitPosition; + cast.cast(object, false); + return true; + } + } + return false; + } + bool blockMeleeAttack(const MWWorld::Ptr &attacker, const MWWorld::Ptr &blocker, const MWWorld::Ptr &weapon, float damage, float attackStrength) { if (!blocker.getClass().hasInventoryStore(blocker)) @@ -215,9 +214,9 @@ namespace MWMechanics damage *= gmst.find("fCombatKODamageMult")->getFloat(); // Apply "On hit" effect of the weapon - bool appliedEnchantment = applyEnchantment(attacker, victim, weapon, hitPosition); + bool appliedEnchantment = applyOnStrikeEnchantment(attacker, victim, weapon, hitPosition); if (weapon != projectile) - appliedEnchantment = applyEnchantment(attacker, victim, projectile, hitPosition); + appliedEnchantment = applyOnStrikeEnchantment(attacker, victim, projectile, hitPosition); if (damage > 0) MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition); diff --git a/apps/openmw/mwmechanics/combat.hpp b/apps/openmw/mwmechanics/combat.hpp index ca78d7956e..7d0b3b78fc 100644 --- a/apps/openmw/mwmechanics/combat.hpp +++ b/apps/openmw/mwmechanics/combat.hpp @@ -6,6 +6,8 @@ namespace MWMechanics { +bool applyOnStrikeEnchantment(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& object, const osg::Vec3f& hitPosition); + /// @return can we block the attack? bool blockMeleeAttack (const MWWorld::Ptr& attacker, const MWWorld::Ptr& blocker, const MWWorld::Ptr& weapon, float damage, float attackStrength); diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 6933c40a3f..be51cea25d 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -17,12 +17,12 @@ namespace MWMechanics int CreatureStats::sActorId = 0; CreatureStats::CreatureStats() - : mDrawState (DrawState_Nothing), mDead (false), mDied (false), mMurdered(false), mFriendlyHits (0), + : mDrawState (DrawState_Nothing), mDead (false), mDeathAnimationFinished(false), mDied (false), mMurdered(false), mFriendlyHits (0), mTalkedTo (false), mAlarmed (false), mAttacked (false), mKnockdown(false), mKnockdownOneFrame(false), mKnockdownOverOneFrame(false), mHitRecovery(false), mBlock(false), mMovementFlags(0), mFallHeight(0), mRecalcMagicka(false), mLastRestock(0,0), mGoldPool(0), mActorId(-1), - mDeathAnimation(0), mLevel (0) + mDeathAnimation(-1), mTimeOfDeath(), mLevel (0) { for (int i=0; i<4; ++i) mAiSettings[i] = 0; @@ -157,7 +157,9 @@ namespace MWMechanics int endurance = getAttribute(ESM::Attribute::Endurance).getModified(); DynamicStat fatigue = getFatigue(); float diff = (strength+willpower+agility+endurance) - fatigue.getBase(); - fatigue.modify(diff); + float currentToBaseRatio = (fatigue.getCurrent() / fatigue.getBase()); + fatigue.setModified(fatigue.getModified() + diff, 0); + fatigue.setCurrent(fatigue.getBase() * currentToBaseRatio); setFatigue(fatigue); } } @@ -187,6 +189,9 @@ namespace MWMechanics if (index==0 && mDynamic[index].getCurrent()<1) { + if (!mDead) + mTimeOfDeath = MWBase::Environment::get().getWorld()->getTimeStamp(); + mDead = true; mDynamic[index].setModifier(0); @@ -233,6 +238,16 @@ namespace MWMechanics return mDead; } + bool CreatureStats::isDeathAnimationFinished() const + { + return mDeathAnimationFinished; + } + + void CreatureStats::setDeathAnimationFinished(bool finished) + { + mDeathAnimationFinished = finished; + } + void CreatureStats::notifyDied() { mDied = true; @@ -272,6 +287,7 @@ namespace MWMechanics mDynamic[0].setCurrent(mDynamic[0].getModified()); mDead = false; + mDeathAnimationFinished = false; } } @@ -479,6 +495,7 @@ namespace MWMechanics state.mGoldPool = mGoldPool; state.mDead = mDead; + state.mDeathAnimationFinished = mDeathAnimationFinished; state.mDied = mDied; state.mMurdered = mMurdered; // The vanilla engine does not store friendly hits in the save file. Since there's no other mechanism @@ -503,6 +520,7 @@ namespace MWMechanics state.mLevel = mLevel; state.mActorId = mActorId; state.mDeathAnimation = mDeathAnimation; + state.mTimeOfDeath = mTimeOfDeath.toEsm(); mSpells.writeState(state.mSpells); mActiveSpells.writeState(state.mActiveSpells); @@ -529,6 +547,7 @@ namespace MWMechanics mGoldPool = state.mGoldPool; mDead = state.mDead; + mDeathAnimationFinished = state.mDeathAnimationFinished; mDied = state.mDied; mMurdered = state.mMurdered; mTalkedTo = state.mTalkedTo; @@ -549,6 +568,7 @@ namespace MWMechanics mLevel = state.mLevel; mActorId = state.mActorId; mDeathAnimation = state.mDeathAnimation; + mTimeOfDeath = MWWorld::TimeStamp(state.mTimeOfDeath); mSpells.readState(state.mSpells); mActiveSpells.readState(state.mActiveSpells); @@ -612,16 +632,21 @@ namespace MWMechanics esm.getHNT(sActorId, "COUN"); } - unsigned char CreatureStats::getDeathAnimation() const + signed char CreatureStats::getDeathAnimation() const { return mDeathAnimation; } - void CreatureStats::setDeathAnimation(unsigned char index) + void CreatureStats::setDeathAnimation(signed char index) { mDeathAnimation = index; } + MWWorld::TimeStamp CreatureStats::getTimeOfDeath() const + { + return mTimeOfDeath; + } + std::map& CreatureStats::getSummonedCreatureMap() { return mSummonedCreatures; diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 46c5bab31e..1718576030 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -34,7 +34,8 @@ namespace MWMechanics Stat mAiSettings[4]; AiSequence mAiSequence; bool mDead; - bool mDied; + bool mDeathAnimationFinished; + bool mDied; // flag for OnDeath script function bool mMurdered; int mFriendlyHits; bool mTalkedTo; @@ -62,8 +63,10 @@ namespace MWMechanics int mActorId; - // The index of the death animation that was played - unsigned char mDeathAnimation; + // The index of the death animation that was played, or -1 if none played + signed char mDeathAnimation; + + MWWorld::TimeStamp mTimeOfDeath; public: typedef std::pair SummonKey; // @@ -159,6 +162,9 @@ namespace MWMechanics bool isDead() const; + bool isDeathAnimationFinished() const; + void setDeathAnimationFinished(bool finished); + void notifyDied(); bool hasDied() const; @@ -256,8 +262,10 @@ namespace MWMechanics void setGoldPool(int pool); int getGoldPool() const; - unsigned char getDeathAnimation() const; - void setDeathAnimation(unsigned char index); + signed char getDeathAnimation() const; // -1 means not decided + void setDeathAnimation(signed char index); + + MWWorld::TimeStamp getTimeOfDeath() const; int getActorId(); ///< Will generate an actor ID, if the actor does not have one yet. diff --git a/apps/openmw/mwmechanics/difficultyscaling.cpp b/apps/openmw/mwmechanics/difficultyscaling.cpp index 950fe33cf4..693587eda3 100644 --- a/apps/openmw/mwmechanics/difficultyscaling.cpp +++ b/apps/openmw/mwmechanics/difficultyscaling.cpp @@ -1,11 +1,11 @@ #include "difficultyscaling.hpp" +#include + #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwworld/esmstore.hpp" -#include - #include "actorutil.hpp" float scaleDamage(float damage, const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim) diff --git a/apps/openmw/mwmechanics/disease.hpp b/apps/openmw/mwmechanics/disease.hpp index 25bde56ab8..5746af2a2f 100644 --- a/apps/openmw/mwmechanics/disease.hpp +++ b/apps/openmw/mwmechanics/disease.hpp @@ -6,14 +6,15 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" + #include "../mwworld/ptr.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" + #include "spells.hpp" #include "creaturestats.hpp" #include "actorutil.hpp" - namespace MWMechanics { diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index 102e3787ad..57ece6a793 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -6,6 +6,7 @@ #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" + #include "../mwbase/mechanicsmanager.hpp" #include "creaturestats.hpp" diff --git a/apps/openmw/mwmechanics/enchanting.hpp b/apps/openmw/mwmechanics/enchanting.hpp index 2c05d2d2e9..a53027bb9c 100644 --- a/apps/openmw/mwmechanics/enchanting.hpp +++ b/apps/openmw/mwmechanics/enchanting.hpp @@ -1,10 +1,15 @@ #ifndef GAME_MWMECHANICS_ENCHANTING_H #define GAME_MWMECHANICS_ENCHANTING_H + #include -#include "../mwworld/ptr.hpp" + #include + +#include "../mwworld/ptr.hpp" + #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" + namespace MWMechanics { class Enchanting diff --git a/apps/openmw/mwmechanics/levelledlist.hpp b/apps/openmw/mwmechanics/levelledlist.hpp index edfef53581..fb06d89357 100644 --- a/apps/openmw/mwmechanics/levelledlist.hpp +++ b/apps/openmw/mwmechanics/levelledlist.hpp @@ -1,16 +1,18 @@ #ifndef OPENMW_MECHANICS_LEVELLEDLIST_H #define OPENMW_MECHANICS_LEVELLEDLIST_H -#include - #include +#include + #include "../mwworld/ptr.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/manualref.hpp" #include "../mwworld/class.hpp" + #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" + #include "creaturestats.hpp" #include "actorutil.hpp" diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index b98e5daa36..278a5749e2 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -11,18 +11,16 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/player.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/dialoguemanager.hpp" -#include "../mwworld/class.hpp" -#include "../mwworld/player.hpp" - -#include "../mwmechanics/aicombat.hpp" -#include "../mwmechanics/aipursue.hpp" - +#include "aicombat.hpp" +#include "aipursue.hpp" #include "spellcasting.hpp" #include "autocalcspell.hpp" #include "npcstats.hpp" @@ -312,7 +310,7 @@ namespace MWMechanics } MechanicsManager::MechanicsManager() - : mWatchedStatsEmpty (true), mUpdatePlayer (true), mClassSelected (false), + : mWatchedTimeToStartDrowning(0), mWatchedStatsEmpty (true), mUpdatePlayer (true), mClassSelected (false), mRaceSelected (false), mAI(true) { //buildPlayer no longer here, needs to be done explicitely after all subsystems are up and running @@ -374,40 +372,40 @@ namespace MWMechanics const MWMechanics::NpcStats &stats = mWatched.getClass().getNpcStats(mWatched); for(int i = 0;i < ESM::Attribute::Length;++i) { - if(stats.getAttribute(i) != mWatchedStats.getAttribute(i) || mWatchedStatsEmpty) + if(stats.getAttribute(i) != mWatchedAttributes[i] || mWatchedStatsEmpty) { std::stringstream attrname; attrname << "AttribVal"<<(i+1); - mWatchedStats.setAttribute(i, stats.getAttribute(i)); + mWatchedAttributes[i] = stats.getAttribute(i); winMgr->setValue(attrname.str(), stats.getAttribute(i)); } } - if(stats.getHealth() != mWatchedStats.getHealth() || mWatchedStatsEmpty) + if(stats.getHealth() != mWatchedHealth || mWatchedStatsEmpty) { static const std::string hbar("HBar"); - mWatchedStats.setHealth(stats.getHealth()); + mWatchedHealth = stats.getHealth(); winMgr->setValue(hbar, stats.getHealth()); } - if(stats.getMagicka() != mWatchedStats.getMagicka() || mWatchedStatsEmpty) + if(stats.getMagicka() != mWatchedMagicka || mWatchedStatsEmpty) { static const std::string mbar("MBar"); - mWatchedStats.setMagicka(stats.getMagicka()); + mWatchedMagicka = stats.getMagicka(); winMgr->setValue(mbar, stats.getMagicka()); } - if(stats.getFatigue() != mWatchedStats.getFatigue() || mWatchedStatsEmpty) + if(stats.getFatigue() != mWatchedFatigue || mWatchedStatsEmpty) { static const std::string fbar("FBar"); - mWatchedStats.setFatigue(stats.getFatigue()); + mWatchedFatigue = stats.getFatigue(); winMgr->setValue(fbar, stats.getFatigue()); } - if(stats.getTimeToStartDrowning() != mWatchedStats.getTimeToStartDrowning()) + if(stats.getTimeToStartDrowning() != mWatchedTimeToStartDrowning) { const float fHoldBreathTime = MWBase::Environment::get().getWorld()->getStore().get() .find("fHoldBreathTime")->getFloat(); - mWatchedStats.setTimeToStartDrowning(stats.getTimeToStartDrowning()); + mWatchedTimeToStartDrowning = stats.getTimeToStartDrowning(); if(stats.getTimeToStartDrowning() >= fHoldBreathTime) winMgr->setDrowningBarVisibility(false); else @@ -422,10 +420,10 @@ namespace MWMechanics //Loop over ESM::Skill::SkillEnum for(int i = 0; i < ESM::Skill::Length; ++i) { - if(stats.getSkill(i) != mWatchedStats.getSkill(i) || mWatchedStatsEmpty) + if(stats.getSkill(i) != mWatchedSkills[i] || mWatchedStatsEmpty) { update = true; - mWatchedStats.getSkill(i) = stats.getSkill(i); + mWatchedSkills[i] = stats.getSkill(i); winMgr->setValue((ESM::Skill::SkillEnum)i, stats.getSkill(i)); } } @@ -501,8 +499,7 @@ namespace MWMechanics void MechanicsManager::rest(bool sleep) { - mActors.restoreDynamicStats (sleep); - mActors.fastForwardAi(); + mActors.rest(sleep); } int MechanicsManager::getHoursToRest() const @@ -581,7 +578,7 @@ namespace MWMechanics mUpdatePlayer = true; } - int MechanicsManager::getDerivedDisposition(const MWWorld::Ptr& ptr) + int MechanicsManager::getDerivedDisposition(const MWWorld::Ptr& ptr, bool addTemporaryDispositionChange) { const MWMechanics::NpcStats& npcSkill = ptr.getClass().getNpcStats(ptr); float x = static_cast(npcSkill.getBaseDisposition()); @@ -653,6 +650,9 @@ namespace MWMechanics x += ptr.getClass().getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Charm).getMagnitude(); + if(addTemporaryDispositionChange) + x += MWBase::Environment::get().getDialogueManager()->getTemporaryDispositionChange(); + int effective_disposition = std::max(0,std::min(int(x),100));//, normally clamped to [0..100] when used return effective_disposition; } @@ -667,10 +667,9 @@ namespace MWMechanics MWWorld::Ptr playerPtr = getPlayer(); const MWMechanics::NpcStats &playerStats = playerPtr.getClass().getNpcStats(playerPtr); - // I suppose the temporary disposition change _has_ to be considered here, + // I suppose the temporary disposition change (second param to getDerivedDisposition()) _has_ to be considered here, // otherwise one would get different prices when exiting and re-entering the dialogue window... - int clampedDisposition = std::max(0, std::min(getDerivedDisposition(ptr) - + MWBase::Environment::get().getDialogueManager()->getTemporaryDispositionChange(),100)); + int clampedDisposition = getDerivedDisposition(ptr); float a = static_cast(std::min(playerStats.getSkill(ESM::Skill::Mercantile).getModified(), 100)); float b = std::min(0.1f * playerStats.getAttribute(ESM::Attribute::Luck).getModified(), 10.f); float c = std::min(0.2f * playerStats.getAttribute(ESM::Attribute::Personality).getModified(), 10.f); @@ -700,8 +699,7 @@ namespace MWMechanics return mActors.countDeaths (id); } - void MechanicsManager::getPersuasionDispositionChange (const MWWorld::Ptr& npc, PersuasionType type, - float currentTemporaryDispositionDelta, bool& success, float& tempChange, float& permChange) + void MechanicsManager::getPersuasionDispositionChange (const MWWorld::Ptr& npc, PersuasionType type, bool& success, float& tempChange, float& permChange) { const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); @@ -717,7 +715,7 @@ namespace MWMechanics float playerRating1, playerRating2, playerRating3; getPersuasionRatings(playerStats, playerRating1, playerRating2, playerRating3, true); - int currentDisposition = std::min(100, std::max(0, int(getDerivedDisposition(npc) + currentTemporaryDispositionDelta))); + int currentDisposition = getDerivedDisposition(npc); float d = 1 - 0.02f * abs(currentDisposition - 50); float target1 = d * (playerRating1 - npcRating1 + 50); @@ -927,7 +925,7 @@ namespace MWMechanics return true; } - if(MWMechanics::isPlayerInCombat()) { + if(MWBase::Environment::get().getWorld()->getPlayer().enemiesNearby()) { MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage2}"); return true; } @@ -1330,7 +1328,7 @@ namespace MWMechanics { if ((*it)->getTypeId() == AiPackage::TypeIdCombat) { - MWWorld::Ptr target = static_cast(*it)->getTarget(); + MWWorld::Ptr target = (*it)->getTarget(); if (!target.isEmpty() && target.getClass().isNpc()) isFightingNpc = true; } @@ -1435,7 +1433,7 @@ namespace MWMechanics osg::Vec3f observerDir = (observer.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0)); float angleRadians = std::acos(observerDir * vec / (observerDir.length() * vec.length())); - if (angleRadians < osg::DegreesToRadians(90.f)) + if (angleRadians > osg::DegreesToRadians(90.f)) y = obsTerm * observerStats.getFatigueTerm() * fSneakNoViewMult; else y = obsTerm * observerStats.getFatigueTerm() * fSneakViewMult; @@ -1506,6 +1504,10 @@ namespace MWMechanics return mActors.getActorsFighting(actor); } + std::list MechanicsManager::getEnemiesNearby(const MWWorld::Ptr& actor) { + return mActors.getEnemiesNearby(actor); + } + int MechanicsManager::countSavedGameRecords() const { return 1 // Death counter @@ -1539,13 +1541,15 @@ namespace MWMechanics { mActors.clear(); mStolenItems.clear(); + mClassSelected = false; + mRaceSelected = false; } bool MechanicsManager::isAggressive(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target) { int disposition = 50; if (ptr.getClass().isNpc()) - disposition = getDerivedDisposition(ptr); + disposition = getDerivedDisposition(ptr, false); int fight = std::max(0, ptr.getClass().getCreatureStats(ptr).getAiSetting(CreatureStats::AI_Fight).getModified() + static_cast(getFightDistanceBias(ptr, target) + getFightDispositionBias(static_cast(disposition)))); @@ -1556,7 +1560,7 @@ namespace MWMechanics (target == getPlayer() && MWBase::Environment::get().getWorld()->getGlobalInt("pcknownwerewolf"))) { - const ESM::GameSetting * iWerewolfFightMod = MWBase::Environment::get().getWorld()->getStore().get().search("iWerewolfFightMod"); + const ESM::GameSetting * iWerewolfFightMod = MWBase::Environment::get().getWorld()->getStore().get().find("iWerewolfFightMod"); fight += iWerewolfFightMod->getInt(); } } @@ -1569,16 +1573,7 @@ namespace MWMechanics MWWorld::Ptr player = getPlayer(); CreatureStats& stats = player.getClass().getCreatureStats(player); if (stats.isDead()) - { - MWMechanics::DynamicStat stat (stats.getHealth()); - - if (stat.getModified()<1) - { - stat.setModified(1, 0); - stats.setHealth(stat); - } stats.resurrect(); - } } bool MechanicsManager::isReadyToBlock(const MWWorld::Ptr &ptr) const @@ -1693,4 +1688,9 @@ namespace MWMechanics stats.getSkill(ESM::Skill::Acrobatics).setBase(gmst.find("fWerewolfAcrobatics")->getInt()); } + void MechanicsManager::cleanupSummonedCreature(const MWWorld::Ptr &caster, int creatureActorId) + { + mActors.cleanupSummonedCreature(caster.getClass().getCreatureStats(caster), creatureActorId); + } + } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index fb29c07ea2..b0a9380e54 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -20,7 +20,16 @@ namespace MWMechanics class MechanicsManager : public MWBase::MechanicsManager { MWWorld::Ptr mWatched; - NpcStats mWatchedStats; + + AttributeValue mWatchedAttributes[8]; + SkillValue mWatchedSkills[27]; + + DynamicStat mWatchedHealth; + DynamicStat mWatchedMagicka; + DynamicStat mWatchedFatigue; + + float mWatchedTimeToStartDrowning; + bool mWatchedStatsEmpty; bool mUpdatePlayer; bool mClassSelected; @@ -92,15 +101,13 @@ namespace MWMechanics virtual int getBarterOffer(const MWWorld::Ptr& ptr,int basePrice, bool buying); ///< This is used by every service to determine the price of objects given the trading skills of the player and NPC. - virtual int getDerivedDisposition(const MWWorld::Ptr& ptr); + virtual int getDerivedDisposition(const MWWorld::Ptr& ptr, bool addTemporaryDispositionChange = true); ///< Calculate the diposition of an NPC toward the player. virtual int countDeaths (const std::string& id) const; ///< Return the number of deaths for actors with the given ID. - virtual void getPersuasionDispositionChange (const MWWorld::Ptr& npc, PersuasionType type, - float currentTemporaryDispositionDelta, bool& success, float& tempChange, float& permChange); - void toLower(std::string npcFaction); + virtual void getPersuasionDispositionChange (const MWWorld::Ptr& npc, PersuasionType type, bool& success, float& tempChange, float& permChange); ///< Perform a persuasion action on NPC /// Check if \a observer is potentially aware of \a ptr. Does not do a line of sight check! @@ -155,6 +162,7 @@ namespace MWMechanics virtual std::list getActorsFollowingIndices(const MWWorld::Ptr& actor); virtual std::list getActorsFighting(const MWWorld::Ptr& actor); + virtual std::list getEnemiesNearby(const MWWorld::Ptr& actor); virtual bool toggleAI(); virtual bool isAIActive(); @@ -190,6 +198,8 @@ namespace MWMechanics virtual void setWerewolf(const MWWorld::Ptr& actor, bool werewolf); virtual void applyWerewolfAcrobatics(const MWWorld::Ptr& actor); + virtual void cleanupSummonedCreature(const MWWorld::Ptr& caster, int creatureActorId); + private: void reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, OffenseType type, int arg=0); diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 10d603ff14..8bea109d09 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -34,6 +34,7 @@ MWMechanics::NpcStats::NpcStats() , mIsWerewolf(false) { mSkillIncreases.resize (ESM::Attribute::Length, 0); + mSpecIncreases.resize(3, 0); } int MWMechanics::NpcStats::getBaseDisposition() const @@ -255,6 +256,8 @@ void MWMechanics::NpcStats::increaseSkill(int skillIndex, const ESM::Class &clas MWBase::Environment::get().getWorld ()->getStore ().get().find(skillIndex); mSkillIncreases[skill->mData.mAttribute] += increase; + mSpecIncreases[skill->mData.mSpecialization] += gmst.find("iLevelupSpecialization")->getInt(); + // Play sound & skill progress notification /// \todo check if character is the player, if levelling is ever implemented for NPCs MWBase::Environment::get().getSoundManager ()->playSound ("skillraise", 1, 1); @@ -326,6 +329,11 @@ int MWMechanics::NpcStats::getLevelupAttributeMultiplier(int attribute) const return MWBase::Environment::get().getWorld()->getStore().get().find(gmst.str())->getInt(); } +int MWMechanics::NpcStats::getSkillIncreasesForSpecialization(int spec) const +{ + return mSpecIncreases[spec]; +} + void MWMechanics::NpcStats::flagAsUsed (const std::string& id) { mUsedIds.insert (id); @@ -469,6 +477,9 @@ void MWMechanics::NpcStats::writeState (ESM::NpcStats& state) const for (int i=0; i::const_iterator iter (state.mUsedIds.begin()); iter!=state.mUsedIds.end(); ++iter) if (store.find (*iter)) diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index 8a98b1d1d1..9ded47c07f 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -34,7 +34,8 @@ namespace MWMechanics std::set mExpelled; std::map mFactionReputation; int mLevelProgress; // 0-10 - std::vector mSkillIncreases; // number of skill increases for each attribute + std::vector mSkillIncreases; // number of skill increases for each attribute (resets after leveling up) + std::vector mSpecIncreases; // number of skill increases for each specialization (accumulates throughout the entire game) std::set mUsedIds; // --------------------------------------------------------------------------- @@ -86,6 +87,8 @@ namespace MWMechanics int getLevelupAttributeMultiplier(int attribute) const; + int getSkillIncreasesForSpecialization(int spec) const; + void levelUp(); void updateHealth(); diff --git a/apps/openmw/mwmechanics/objects.cpp b/apps/openmw/mwmechanics/objects.cpp index 94b552a38f..7d7c0bfaea 100644 --- a/apps/openmw/mwmechanics/objects.cpp +++ b/apps/openmw/mwmechanics/objects.cpp @@ -2,11 +2,11 @@ #include -#include "movement.hpp" - #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "movement.hpp" + namespace MWMechanics { diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp index f0ffc92e19..5d99fe7236 100644 --- a/apps/openmw/mwmechanics/obstacle.cpp +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -120,19 +120,15 @@ namespace MWMechanics * u = how long to move sideways * */ - bool ObstacleCheck::check(const MWWorld::Ptr& actor, float duration) + bool ObstacleCheck::check(const MWWorld::Ptr& actor, float duration, float scaleMinimumDistance) { const MWWorld::Class& cls = actor.getClass(); ESM::Position pos = actor.getRefData().getPosition(); - // actors can move at most 60 fps (the physics framerate). - // the max() can be removed if we implement physics interpolation. - float movementDuration = std::max(1/60.f, duration); - if(mDistSameSpot == -1) - mDistSameSpot = DIST_SAME_SPOT * cls.getSpeed(actor); + mDistSameSpot = DIST_SAME_SPOT * cls.getSpeed(actor) * scaleMinimumDistance; - float distSameSpot = mDistSameSpot * movementDuration; + float distSameSpot = mDistSameSpot * duration; bool samePosition = (osg::Vec2f(pos.pos[0], pos.pos[1]) - osg::Vec2f(mPrevX, mPrevY)).length2() < distSameSpot * distSameSpot; diff --git a/apps/openmw/mwmechanics/obstacle.hpp b/apps/openmw/mwmechanics/obstacle.hpp index 37ccf72b2d..d2e1cfc2ee 100644 --- a/apps/openmw/mwmechanics/obstacle.hpp +++ b/apps/openmw/mwmechanics/obstacle.hpp @@ -37,7 +37,7 @@ namespace MWMechanics // Returns true if there is an obstacle and an evasive action // should be taken - bool check(const MWWorld::Ptr& actor, float duration); + bool check(const MWWorld::Ptr& actor, float duration, float scaleMinimumDistance = 1.0f); // change direction to try to fix "stuck" actor void takeEvasiveAction(MWMechanics::Movement& actorMovement); diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 48d306c52e..a3e4da76f3 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -1,4 +1,5 @@ #include "pathfinding.hpp" + #include #include "../mwbase/world.hpp" @@ -6,47 +7,11 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" + #include "coordinateconverter.hpp" namespace { - // Slightly cheaper version for comparisons. - // Caller needs to be careful for very short distances (i.e. less than 1) - // or when accumuating the results i.e. (a + b)^2 != a^2 + b^2 - // - float distanceSquared(ESM::Pathgrid::Point point, const osg::Vec3f& pos) - { - return (MWMechanics::PathFinder::MakeOsgVec3(point) - pos).length2(); - } - - // Return the closest pathgrid point index from the specified position co - // -ordinates. NOTE: Does not check if there is a sensible way to get there - // (e.g. a cliff in front). - // - // NOTE: pos is expected to be in local co-ordinates, as is grid->mPoints - // - int getClosestPoint(const ESM::Pathgrid* grid, const osg::Vec3f& pos) - { - assert(grid && !grid->mPoints.empty()); - - float distanceBetween = distanceSquared(grid->mPoints[0], pos); - int closestIndex = 0; - - // TODO: if this full scan causes performance problems mapping pathgrid - // points to a quadtree may help - for(unsigned int counter = 1; counter < grid->mPoints.size(); counter++) - { - float potentialDistBetween = distanceSquared(grid->mPoints[counter], pos); - if(potentialDistBetween < distanceBetween) - { - distanceBetween = potentialDistBetween; - closestIndex = counter; - } - } - - return closestIndex; - } - // Chooses a reachable end pathgrid point. start is assumed reachable. std::pair getClosestReachablePoint(const ESM::Pathgrid* grid, const MWWorld::CellStore *cell, @@ -62,7 +27,7 @@ namespace // points to a quadtree may help for(unsigned int counter = 0; counter < grid->mPoints.size(); counter++) { - float potentialDistBetween = distanceSquared(grid->mPoints[counter], pos); + float potentialDistBetween = MWMechanics::PathFinder::DistanceSquared(grid->mPoints[counter], pos); if (potentialDistBetween < closestDistanceReachable) { // found a closer one @@ -188,7 +153,7 @@ namespace MWMechanics * pathgrid point (e.g. wander) then it may be worth while to call * pop_back() to remove the redundant entry. * - * NOTE: co-ordinates must be converted prior to calling getClosestPoint() + * NOTE: co-ordinates must be converted prior to calling GetClosestPoint() * * | * | cell @@ -228,16 +193,16 @@ namespace MWMechanics return; } - // NOTE: getClosestPoint expects local co-ordinates + // NOTE: GetClosestPoint expects local co-ordinates CoordinateConverter converter(mCell->getCell()); - // NOTE: It is possible that getClosestPoint returns a pathgrind point index + // NOTE: It is possible that GetClosestPoint returns a pathgrind point index // that is unreachable in some situations. e.g. actor is standing // outside an area enclosed by walls, but there is a pathgrid // point right behind the wall that is closer than any pathgrid // point outside the wall osg::Vec3f startPointInLocalCoords(converter.toLocalVec3(startPoint)); - int startNode = getClosestPoint(mPathgrid, startPointInLocalCoords); + int startNode = GetClosestPoint(mPathgrid, startPointInLocalCoords); osg::Vec3f endPointInLocalCoords(converter.toLocalVec3(endPoint)); std::pair endNode = getClosestReachablePoint(mPathgrid, cell, @@ -247,8 +212,8 @@ namespace MWMechanics // if it's shorter for actor to travel from start to end, than to travel from either // start or end to nearest pathgrid point, just travel from start to end. float startToEndLength2 = (endPointInLocalCoords - startPointInLocalCoords).length2(); - float endTolastNodeLength2 = distanceSquared(mPathgrid->mPoints[endNode.first], endPointInLocalCoords); - float startTo1stNodeLength2 = distanceSquared(mPathgrid->mPoints[startNode], startPointInLocalCoords); + float endTolastNodeLength2 = DistanceSquared(mPathgrid->mPoints[endNode.first], endPointInLocalCoords); + float startTo1stNodeLength2 = DistanceSquared(mPathgrid->mPoints[startNode], startPointInLocalCoords); if ((startToEndLength2 < startTo1stNodeLength2) || (startToEndLength2 < endTolastNodeLength2)) { mPath.push_back(endPoint); diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 6281bb072c..05f8469913 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -1,9 +1,11 @@ #ifndef GAME_MWMECHANICS_PATHFINDING_H #define GAME_MWMECHANICS_PATHFINDING_H +#include +#include + #include #include -#include namespace MWWorld { @@ -110,6 +112,43 @@ namespace MWMechanics { return osg::Vec3f(static_cast(p.mX), static_cast(p.mY), static_cast(p.mZ)); } + + // Slightly cheaper version for comparisons. + // Caller needs to be careful for very short distances (i.e. less than 1) + // or when accumuating the results i.e. (a + b)^2 != a^2 + b^2 + // + static float DistanceSquared(ESM::Pathgrid::Point point, const osg::Vec3f& pos) + { + return (MWMechanics::PathFinder::MakeOsgVec3(point) - pos).length2(); + } + + // Return the closest pathgrid point index from the specified position co + // -ordinates. NOTE: Does not check if there is a sensible way to get there + // (e.g. a cliff in front). + // + // NOTE: pos is expected to be in local co-ordinates, as is grid->mPoints + // + static int GetClosestPoint(const ESM::Pathgrid* grid, const osg::Vec3f& pos) + { + assert(grid && !grid->mPoints.empty()); + + float distanceBetween = DistanceSquared(grid->mPoints[0], pos); + int closestIndex = 0; + + // TODO: if this full scan causes performance problems mapping pathgrid + // points to a quadtree may help + for(unsigned int counter = 1; counter < grid->mPoints.size(); counter++) + { + float potentialDistBetween = DistanceSquared(grid->mPoints[counter], pos); + if(potentialDistBetween < distanceBetween) + { + distanceBetween = potentialDistBetween; + closestIndex = counter; + } + } + + return closestIndex; + } private: std::list mPath; diff --git a/apps/openmw/mwmechanics/pathgrid.hpp b/apps/openmw/mwmechanics/pathgrid.hpp index 67fbacdeda..ab418ae78b 100644 --- a/apps/openmw/mwmechanics/pathgrid.hpp +++ b/apps/openmw/mwmechanics/pathgrid.hpp @@ -1,9 +1,10 @@ #ifndef GAME_MWMECHANICS_PATHGRID_H #define GAME_MWMECHANICS_PATHGRID_H -#include #include +#include + namespace ESM { struct Cell; diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index ab8784beb1..4059702d35 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -19,14 +19,12 @@ #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" - #include "../mwworld/inventorystore.hpp" #include "../mwrender/animation.hpp" #include "magiceffects.hpp" #include "npcstats.hpp" -#include "summoning.hpp" #include "actorutil.hpp" namespace @@ -62,13 +60,6 @@ namespace } } - void applyDynamicStatsEffect(int attribute, const MWWorld::Ptr& target, float magnitude) - { - MWMechanics::DynamicStat value = target.getClass().getCreatureStats(target).getDynamic(attribute); - value.setCurrent(value.getCurrent()+magnitude, attribute == 2); - target.getClass().getCreatureStats(target).setDynamic(attribute, value); - } - } namespace MWMechanics @@ -378,6 +369,11 @@ namespace MWMechanics if (!checkEffectTarget(effectIt->mEffectID, target, castByPlayer)) continue; + // caster needs to be an actor for linked effects (e.g. Absorb) + if (magicEffect->mData.mFlags & ESM::MagicEffect::CasterLinked + && (caster.isEmpty() || !caster.getClass().isActor())) + continue; + // If player is healing someone, show the target's HP bar if (castByPlayer && target != caster && effectIt->mEffectID == ESM::MagicEffect::RestoreHealth @@ -512,7 +508,7 @@ namespace MWMechanics std::map::iterator found = targetStats.getSummonedCreatureMap().find(std::make_pair(effectIt->mEffectID, mId)); if (found != targetStats.getSummonedCreatureMap().end()) { - cleanupSummonedCreature(targetStats, found->second); + MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(target, found->second); targetStats.getSummonedCreatureMap().erase(found); } } @@ -552,7 +548,7 @@ namespace MWMechanics } if (!exploded) - MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, range, mId, mSourceName); + MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, target, range, mId, mSourceName); if (!reflectedEffects.mList.empty()) inflict(caster, target, reflectedEffects, range, true, exploded); @@ -689,7 +685,7 @@ namespace MWMechanics throw std::runtime_error("ID type cannot be casted"); } - bool CastSpell::cast(const MWWorld::Ptr &item) + bool CastSpell::cast(const MWWorld::Ptr &item, bool launchProjectile) { std::string enchantmentName = item.getClass().getEnchantment(item); if (enchantmentName.empty()) @@ -756,15 +752,20 @@ namespace MWMechanics inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch); } - std::string projectileModel; - std::string sound; - float speed = 0; - getProjectileInfo(enchantment->mEffects, projectileModel, sound, speed); - if (!projectileModel.empty()) - MWBase::Environment::get().getWorld()->launchMagicBolt(projectileModel, sound, mId, speed, - false, enchantment->mEffects, mCaster, mSourceName, - // Not needed, enchantments can only be cast by actors - osg::Vec3f(1,0,0)); + if (launchProjectile) + { + std::string projectileModel; + std::string sound; + float speed = 0; + getProjectileInfo(enchantment->mEffects, projectileModel, sound, speed); + if (!projectileModel.empty()) + MWBase::Environment::get().getWorld()->launchMagicBolt(projectileModel, sound, mId, speed, + false, enchantment->mEffects, mCaster, mSourceName, + // Not needed, enchantments can only be cast by actors + osg::Vec3f(1,0,0)); + } + else if (!mTarget.isEmpty()) + inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Target); return true; } diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 5b48bd4a8d..ae20a39d01 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -1,10 +1,10 @@ #ifndef MWMECHANICS_SPELLSUCCESS_H #define MWMECHANICS_SPELLSUCCESS_H -#include "../mwworld/ptr.hpp" - #include +#include "../mwworld/ptr.hpp" + namespace ESM { struct Spell; @@ -81,7 +81,8 @@ namespace MWMechanics bool cast (const ESM::Spell* spell); /// @note mCaster must be an actor - bool cast (const MWWorld::Ptr& item); + /// @param launchProjectile If set to false, "on target" effects are directly applied instead of being launched as projectile originating from the caster. + bool cast (const MWWorld::Ptr& item, bool launchProjectile=true); /// @note mCaster must be an NPC bool cast (const ESM::Ingredient* ingredient); diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index a88b6b2633..ff397f69d8 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -15,6 +15,11 @@ namespace MWMechanics { + Spells::Spells() + : mSpellsChanged(false) + { + } + Spells::TIterator Spells::begin() const { return mSpells.begin(); @@ -30,6 +35,44 @@ namespace MWMechanics return MWBase::Environment::get().getWorld()->getStore().get().find(id); } + void Spells::rebuildEffects() const + { + mEffects = MagicEffects(); + mSourcedEffects.clear(); + + for (TIterator iter = mSpells.begin(); iter!=mSpells.end(); ++iter) + { + const ESM::Spell *spell = iter->first; + + if (spell->mData.mType==ESM::Spell::ST_Ability || spell->mData.mType==ESM::Spell::ST_Blight || + spell->mData.mType==ESM::Spell::ST_Disease || spell->mData.mType==ESM::Spell::ST_Curse) + { + int i=0; + for (std::vector::const_iterator it = spell->mEffects.mList.begin(); it != spell->mEffects.mList.end(); ++it) + { + if (iter->second.mPurgedEffects.find(i) != iter->second.mPurgedEffects.end()) + continue; // effect was purged + + float random = 1.f; + if (iter->second.mEffectRands.find(i) != iter->second.mEffectRands.end()) + random = iter->second.mEffectRands.at(i); + + float magnitude = it->mMagnMin + (it->mMagnMax - it->mMagnMin) * random; + mEffects.add (*it, magnitude); + mSourcedEffects[spell].add(MWMechanics::EffectKey(*it), magnitude); + + ++i; + } + } + } + + for (std::map::const_iterator it = mPermanentSpellEffects.begin(); it != mPermanentSpellEffects.end(); ++it) + { + mEffects += it->second; + mSourcedEffects[it->first] += it->second; + } + } + bool Spells::hasSpell(const std::string &spell) const { return hasSpell(getSpell(spell)); @@ -44,7 +87,7 @@ namespace MWMechanics { if (mSpells.find (spell)==mSpells.end()) { - std::map random; + std::map random; // Determine the random magnitudes (unless this is a castable spell, in which case // they will be determined when the spell is cast) @@ -66,7 +109,10 @@ namespace MWMechanics mCorprusSpells[spell] = corprus; } - mSpells.insert (std::make_pair (spell, random)); + SpellParams params; + params.mEffectRands = random; + mSpells.insert (std::make_pair (spell, params)); + mSpellsChanged = true; } } @@ -102,7 +148,10 @@ namespace MWMechanics } if (iter!=mSpells.end()) + { mSpells.erase (iter); + mSpellsChanged = true; + } if (spellId==mSelectedSpell) mSelectedSpell.clear(); @@ -110,41 +159,17 @@ namespace MWMechanics MagicEffects Spells::getMagicEffects() const { - // TODO: These are recalculated every frame, no need to do that - - MagicEffects effects; - - for (TIterator iter = mSpells.begin(); iter!=mSpells.end(); ++iter) - { - const ESM::Spell *spell = iter->first; - - if (spell->mData.mType==ESM::Spell::ST_Ability || spell->mData.mType==ESM::Spell::ST_Blight || - spell->mData.mType==ESM::Spell::ST_Disease || spell->mData.mType==ESM::Spell::ST_Curse) - { - int i=0; - for (std::vector::const_iterator it = spell->mEffects.mList.begin(); it != spell->mEffects.mList.end(); ++it) - { - float random = 1.f; - if (iter->second.find(i) != iter->second.end()) - random = iter->second.at(i); - - effects.add (*it, it->mMagnMin + (it->mMagnMax - it->mMagnMin) * random); - ++i; - } - } + if (mSpellsChanged) { + rebuildEffects(); + mSpellsChanged = false; } - - for (std::map::const_iterator it = mPermanentSpellEffects.begin(); it != mPermanentSpellEffects.end(); ++it) - { - effects += it->second; - } - - return effects; + return mEffects; } void Spells::clear() { mSpells.clear(); + mSpellsChanged = true; } void Spells::setSelectedSpell (const std::string& spellId) @@ -200,7 +225,10 @@ namespace MWMechanics { const ESM::Spell *spell = iter->first; if (spell->mData.mType == ESM::Spell::ST_Disease) + { mSpells.erase(iter++); + mSpellsChanged = true; + } else ++iter; } @@ -212,7 +240,10 @@ namespace MWMechanics { const ESM::Spell *spell = iter->first; if (spell->mData.mType == ESM::Spell::ST_Blight && !hasCorprusEffect(spell)) + { mSpells.erase(iter++); + mSpellsChanged = true; + } else ++iter; } @@ -224,7 +255,10 @@ namespace MWMechanics { const ESM::Spell *spell = iter->first; if (hasCorprusEffect(spell)) + { mSpells.erase(iter++); + mSpellsChanged = true; + } else ++iter; } @@ -236,7 +270,10 @@ namespace MWMechanics { const ESM::Spell *spell = iter->first; if (spell->mData.mType == ESM::Spell::ST_Curse) + { mSpells.erase(iter++); + mSpellsChanged = true; + } else ++iter; } @@ -244,27 +281,19 @@ namespace MWMechanics void Spells::visitEffectSources(EffectSourceVisitor &visitor) const { - for (TIterator it = begin(); it != end(); ++it) + if (mSpellsChanged) { + rebuildEffects(); + mSpellsChanged = false; + } + + for (std::map::const_iterator it = mSourcedEffects.begin(); + it != mSourcedEffects.end(); ++it) { - const ESM::Spell* spell = it->first; - - // these are the spell types that are permanently in effect - if (!(spell->mData.mType == ESM::Spell::ST_Ability) - && !(spell->mData.mType == ESM::Spell::ST_Disease) - && !(spell->mData.mType == ESM::Spell::ST_Curse) - && !(spell->mData.mType == ESM::Spell::ST_Blight)) - continue; - const ESM::EffectList& list = spell->mEffects; - int i=0; - for (std::vector::const_iterator effectIt = list.mList.begin(); - effectIt != list.mList.end(); ++effectIt, ++i) + const ESM::Spell * spell = it->first; + for (MagicEffects::Collection::const_iterator effectIt = it->second.begin(); + effectIt != it->second.end(); ++effectIt) { - float random = 1.f; - if (it->second.find(i) != it->second.end()) - random = it->second.at(i); - - float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random; - visitor.visit(MWMechanics::EffectKey(*effectIt), spell->mName, spell->mId, -1, magnitude); + visitor.visit(effectIt->first, spell->mName, spell->mId, -1, effectIt->second.getMagnitude()); } } } @@ -283,12 +312,13 @@ namespace MWMechanics if ((effectIt->mEffectID != ESM::MagicEffect::Corprus) && (magicEffect->mData.mFlags & ESM::MagicEffect::UncappedDamage)) // APPLIED_ONCE { float random = 1.f; - if (mSpells[spell].find(i) != mSpells[spell].end()) - random = mSpells[spell].at(i); + if (mSpells[spell].mEffectRands.find(i) != mSpells[spell].mEffectRands.end()) + random = mSpells[spell].mEffectRands.at(i); float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random; magnitude *= std::max(1, mCorprusSpells[spell].mWorsenings); mPermanentSpellEffects[spell].add(MWMechanics::EffectKey(*effectIt), MWMechanics::EffectParam(magnitude)); + mSpellsChanged = true; } } } @@ -310,6 +340,42 @@ namespace MWMechanics return mCorprusSpells; } + void Spells::purgeEffect(int effectId) + { + for (TContainer::iterator spellIt = mSpells.begin(); spellIt != mSpells.end(); ++spellIt) + { + int i = 0; + for (std::vector::const_iterator effectIt = spellIt->first->mEffects.mList.begin(); effectIt != spellIt->first->mEffects.mList.end(); ++effectIt) + { + if (effectIt->mEffectID == effectId) + { + spellIt->second.mPurgedEffects.insert(i); + mSpellsChanged = true; + } + ++i; + } + } + } + + void Spells::purgeEffect(int effectId, const std::string & sourceId) + { + const ESM::Spell * spell = MWBase::Environment::get().getWorld()->getStore().get().find(sourceId); + TContainer::iterator spellIt = mSpells.find(spell); + if (spellIt == mSpells.end()) + return; + + int i = 0; + for (std::vector::const_iterator effectIt = spellIt->first->mEffects.mList.begin(); effectIt != spellIt->first->mEffects.mList.end(); ++effectIt) + { + if (effectIt->mEffectID == effectId) + { + spellIt->second.mPurgedEffects.insert(i); + mSpellsChanged = true; + } + ++i; + } + } + bool Spells::canUsePower(const ESM::Spell* spell) const { std::map::const_iterator it = mUsedPowers.find(spell); @@ -332,7 +398,8 @@ namespace MWMechanics const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first); if (spell) { - mSpells[spell] = it->second; + mSpells[spell].mEffectRands = it->second.mEffectRands; + mSpells[spell].mPurgedEffects = it->second.mPurgedEffects; if (it->first == state.mSelectedSpell) mSelectedSpell = it->first; @@ -370,12 +437,19 @@ namespace MWMechanics mCorprusSpells[spell].mWorsenings = state.mCorprusSpells.at(it->first).mWorsenings; mCorprusSpells[spell].mNextWorsening = MWWorld::TimeStamp(state.mCorprusSpells.at(it->first).mNextWorsening); } + + mSpellsChanged = true; } void Spells::writeState(ESM::SpellState &state) const { for (TContainer::const_iterator it = mSpells.begin(); it != mSpells.end(); ++it) - state.mSpells.insert(std::make_pair(it->first->mId, it->second)); + { + ESM::SpellState::SpellParams params; + params.mEffectRands = it->second.mEffectRands; + params.mPurgedEffects = it->second.mPurgedEffects; + state.mSpells.insert(std::make_pair(it->first->mId, params)); + } state.mSelectedSpell = mSelectedSpell; diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp index 1b1993d5ef..c1705b38a1 100644 --- a/apps/openmw/mwmechanics/spells.hpp +++ b/apps/openmw/mwmechanics/spells.hpp @@ -3,6 +3,7 @@ #include #include +#include #include @@ -32,8 +33,12 @@ namespace MWMechanics public: typedef const ESM::Spell* SpellKey; + struct SpellParams { + std::map mEffectRands; // + std::set mPurgedEffects; // indices of purged effects + }; - typedef std::map > TContainer; // ID, + typedef std::map TContainer; typedef TContainer::const_iterator TIterator; struct CorprusStats @@ -45,7 +50,6 @@ namespace MWMechanics }; private: - TContainer mSpells; // spell-tied effects that will be applied even after removing the spell (currently used to keep positive effects when corprus is removed) @@ -58,15 +62,24 @@ namespace MWMechanics std::map mCorprusSpells; + mutable bool mSpellsChanged; + mutable MagicEffects mEffects; + mutable std::map mSourcedEffects; + void rebuildEffects() const; + /// Get spell from ID, throws exception if not found const ESM::Spell* getSpell(const std::string& id) const; public: + Spells(); void worsenCorprus(const ESM::Spell* spell); static bool hasCorprusEffect(const ESM::Spell *spell); const std::map & getCorprusSpells() const; + void purgeEffect(int effectId); + void purgeEffect(int effectId, const std::string & sourceId); + bool canUsePower (const ESM::Spell* spell) const; void usePower (const ESM::Spell* spell); diff --git a/apps/openmw/mwmechanics/stat.cpp b/apps/openmw/mwmechanics/stat.cpp index 6eb5e0246d..cf1f228c08 100644 --- a/apps/openmw/mwmechanics/stat.cpp +++ b/apps/openmw/mwmechanics/stat.cpp @@ -33,18 +33,6 @@ namespace MWMechanics mBase = mModified = value; } template - void Stat::modify(const T& diff) - { - mBase += diff; - if(mBase >= static_cast(0)) - mModified += diff; - else - { - mModified += diff - mBase; - mBase = static_cast(0); - } - } - template void Stat::setBase (const T& value) { T diff = value - mBase; @@ -139,12 +127,6 @@ namespace MWMechanics mCurrent = getModified(); } template - void DynamicStat::modify (const T& diff, bool allowCurrentDecreaseBelowZero) - { - mStatic.modify (diff); - setCurrent (getCurrent()+diff, allowCurrentDecreaseBelowZero); - } - template void DynamicStat::setCurrent (const T& value, bool allowDecreaseBelowZero) { if (value > mCurrent) diff --git a/apps/openmw/mwmechanics/stat.hpp b/apps/openmw/mwmechanics/stat.hpp index c075f5fa7b..5b58006344 100644 --- a/apps/openmw/mwmechanics/stat.hpp +++ b/apps/openmw/mwmechanics/stat.hpp @@ -32,7 +32,6 @@ namespace MWMechanics /// Set base and modified to \a value. void set (const T& value); - void modify(const T& diff); /// Set base and adjust modified accordingly. void setBase (const T& value); @@ -85,9 +84,6 @@ namespace MWMechanics /// Set modified value an adjust base accordingly. void setModified (T value, const T& min, const T& max = std::numeric_limits::max()); - /// Change modified relatively. - void modify (const T& diff, bool allowCurrentDecreaseBelowZero=false); - void setCurrent (const T& value, bool allowDecreaseBelowZero = false); void setModifier (const T& modifier, bool allowCurrentDecreaseBelowZero=false); diff --git a/apps/openmw/mwmechanics/summoning.cpp b/apps/openmw/mwmechanics/summoning.cpp index 636e199074..3ff8cf667c 100644 --- a/apps/openmw/mwmechanics/summoning.cpp +++ b/apps/openmw/mwmechanics/summoning.cpp @@ -4,8 +4,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" - -#include "../mwmechanics/spellcasting.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" @@ -14,37 +13,13 @@ #include "../mwrender/animation.hpp" +#include "spellcasting.hpp" #include "creaturestats.hpp" #include "aifollow.hpp" - namespace MWMechanics { - void cleanupSummonedCreature (MWMechanics::CreatureStats& casterStats, int creatureActorId) - { - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(creatureActorId); - if (!ptr.isEmpty()) - { - // TODO: Show death animation before deleting? We shouldn't allow looting the corpse while the animation - // plays though, which is a rather lame exploit in vanilla. - MWBase::Environment::get().getWorld()->deleteObject(ptr); - - const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() - .search("VFX_Summon_End"); - if (fx) - MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel, - "", ptr.getRefData().getPosition().asVec3()); - } - else if (creatureActorId != -1) - { - // We didn't find the creature. It's probably in an inactive cell. - // Add to graveyard so we can delete it when the cell becomes active. - std::vector& graveyard = casterStats.getSummonedCreatureGraveyard(); - graveyard.push_back(creatureActorId); - } - } - UpdateSummonedCreatures::UpdateSummonedCreatures(const MWWorld::Ptr &actor) : mActor(actor) { @@ -63,7 +38,7 @@ namespace MWMechanics } } - void UpdateSummonedCreatures::finish() + void UpdateSummonedCreatures::process() { static std::map summonMap; if (summonMap.empty()) @@ -102,7 +77,7 @@ namespace MWMechanics if (!found) { // Effect has ended - cleanupSummonedCreature(creatureStats, it->second); + MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(mActor, it->second); creatureMap.erase(it++); continue; } @@ -114,31 +89,16 @@ namespace MWMechanics bool found = creatureMap.find(std::make_pair(it->first, it->second)) != creatureMap.end(); if (!found) { - ESM::Position ipos = mActor.getRefData().getPosition(); - osg::Vec3f pos(ipos.asVec3()); - - osg::Quat rot (-ipos.rot[2], osg::Vec3f(0,0,1)); - const float distance = 50; - pos = pos + (rot * osg::Vec3f(0,1,0)) * distance; - ipos.pos[0] = pos.x(); - ipos.pos[1] = pos.y(); - ipos.pos[2] = pos.z(); - ipos.rot[0] = 0; - ipos.rot[1] = 0; - ipos.rot[2] = 0; - const std::string& creatureGmst = summonMap[it->first]; std::string creatureID = MWBase::Environment::get().getWorld()->getStore().get().find(creatureGmst)->getString(); if (!creatureID.empty()) { - MWWorld::CellStore* store = mActor.getCell(); int creatureActorId = -1; try { MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), creatureID, 1); - ref.getPtr().getCellRef().setPosition(ipos); MWMechanics::CreatureStats& summonedCreatureStats = ref.getPtr().getClass().getCreatureStats(ref.getPtr()); @@ -147,7 +107,7 @@ namespace MWMechanics summonedCreatureStats.getAiSequence().stack(package, ref.getPtr()); creatureActorId = summonedCreatureStats.getActorId(); - MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos); + MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(), mActor, mActor.getCell(), 0, 120.f); MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(placed); if (anim) @@ -172,14 +132,14 @@ namespace MWMechanics for (std::map::iterator it = creatureMap.begin(); it != creatureMap.end(); ) { MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->second); - if (!ptr.isEmpty() && ptr.getClass().getCreatureStats(ptr).isDead()) + if (!ptr.isEmpty() && ptr.getClass().getCreatureStats(ptr).isDead() && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished()) { // Purge the magic effect so a new creature can be summoned if desired creatureStats.getActiveSpells().purgeEffect(it->first.first, it->first.second); if (mActor.getClass().hasInventoryStore(ptr)) mActor.getClass().getInventoryStore(mActor).purgeEffect(it->first.first, it->first.second); - cleanupSummonedCreature(creatureStats, it->second); + MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(mActor, it->second); creatureMap.erase(it++); } else diff --git a/apps/openmw/mwmechanics/summoning.hpp b/apps/openmw/mwmechanics/summoning.hpp index 8e418cdeb4..b2a3c60eac 100644 --- a/apps/openmw/mwmechanics/summoning.hpp +++ b/apps/openmw/mwmechanics/summoning.hpp @@ -3,9 +3,10 @@ #include -#include "magiceffects.hpp" #include "../mwworld/ptr.hpp" +#include "magiceffects.hpp" + namespace MWMechanics { @@ -21,7 +22,7 @@ namespace MWMechanics float magnitude, float remainingTime = -1, float totalTime = -1); /// To call after all effect sources have been visited - void finish(); + void process(); private: MWWorld::Ptr mActor; @@ -29,8 +30,6 @@ namespace MWMechanics std::set > mActiveEffects; }; - void cleanupSummonedCreature (MWMechanics::CreatureStats& casterStats, int creatureActorId); - } #endif diff --git a/apps/openmw/mwmechanics/trading.cpp b/apps/openmw/mwmechanics/trading.cpp new file mode 100644 index 0000000000..469b454df5 --- /dev/null +++ b/apps/openmw/mwmechanics/trading.cpp @@ -0,0 +1,84 @@ +#include "trading.hpp" + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/world.hpp" + +#include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" + +#include "creaturestats.hpp" + +namespace MWMechanics +{ + Trading::Trading() {} + + bool Trading::haggle(const MWWorld::Ptr& player, const MWWorld::Ptr& merchant, int playerOffer, int merchantOffer) + { + // accept if merchant offer is better than player offer + if ( playerOffer <= merchantOffer ) { + return true; + } + + // reject if npc is a creature + if ( merchant.getTypeName() != typeid(ESM::NPC).name() ) { + return false; + } + + const MWWorld::Store &gmst = + MWBase::Environment::get().getWorld()->getStore().get(); + + // Is the player buying? + bool buying = (merchantOffer < 0); + + int a = std::abs(merchantOffer); + int b = std::abs(playerOffer); + int d = (buying) + ? int(100 * (a - b) / a) + : int(100 * (b - a) / a); + + int clampedDisposition = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(merchant); + + const MWMechanics::CreatureStats &merchantStats = merchant.getClass().getCreatureStats(merchant); + const MWMechanics::CreatureStats &playerStats = player.getClass().getCreatureStats(player); + + float a1 = static_cast(player.getClass().getSkill(player, ESM::Skill::Mercantile)); + float b1 = 0.1f * playerStats.getAttribute(ESM::Attribute::Luck).getModified(); + float c1 = 0.2f * playerStats.getAttribute(ESM::Attribute::Personality).getModified(); + float d1 = static_cast(merchant.getClass().getSkill(merchant, ESM::Skill::Mercantile)); + float e1 = 0.1f * merchantStats.getAttribute(ESM::Attribute::Luck).getModified(); + float f1 = 0.2f * merchantStats.getAttribute(ESM::Attribute::Personality).getModified(); + + float dispositionTerm = gmst.find("fDispositionMod")->getFloat() * (clampedDisposition - 50); + float pcTerm = (dispositionTerm - 50 + a1 + b1 + c1) * playerStats.getFatigueTerm(); + float npcTerm = (d1 + e1 + f1) * merchantStats.getFatigueTerm(); + float x = gmst.find("fBargainOfferMulti")->getFloat() * d + + gmst.find("fBargainOfferBase")->getFloat() + + std::abs(int(pcTerm - npcTerm)); + + int roll = Misc::Rng::rollDice(100) + 1; + + // reject if roll fails + // (or if player tries to buy things and get money) + if ( roll > x || (merchantOffer < 0 && 0 < playerOffer) ) { + return false; + } + + // apply skill gain on successful barter + float skillGain = 0.f; + int finalPrice = std::abs(playerOffer); + int initialMerchantOffer = std::abs(merchantOffer); + + if ( !buying && (finalPrice > initialMerchantOffer) ) { + skillGain = floor(100.f * (finalPrice - initialMerchantOffer) / finalPrice); + } + else if ( buying && (finalPrice < initialMerchantOffer) ) { + skillGain = floor(100.f * (initialMerchantOffer - finalPrice) / initialMerchantOffer); + } + player.getClass().skillUsageSucceeded(player, ESM::Skill::Mercantile, 0, skillGain); + + return true; + } +} diff --git a/apps/openmw/mwmechanics/trading.hpp b/apps/openmw/mwmechanics/trading.hpp new file mode 100644 index 0000000000..e006370dde --- /dev/null +++ b/apps/openmw/mwmechanics/trading.hpp @@ -0,0 +1,17 @@ +#ifndef OPENMW_MECHANICS_TRADING_H +#define OPENMW_MECHANICS_TRADING_H + +#include "../mwworld/ptr.hpp" + +namespace MWMechanics +{ + class Trading + { + public: + Trading(); + + bool haggle(const MWWorld::Ptr& player, const MWWorld::Ptr& merchant, int playerOffer, int merchantOffer); + }; +} + +#endif diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index cc46897d13..61022da28c 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -16,7 +16,7 @@ namespace MWPhysics { -Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr shape, btCollisionWorld* world) +Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr shape, btCollisionWorld* world) : mCanWaterWalk(false), mWalkingOnWater(false) , mCollisionObject(0), mForce(0.f, 0.f, 0.f), mOnGround(false) , mInternalCollisionMode(true) @@ -45,8 +45,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptrgetWorldTransform(); osg::Vec3f scaledTranslation = mRotation * osg::componentMultiply(mMeshTranslation, mScale); - osg::Vec3f newPosition = scaledTranslation + position; - + osg::Vec3f newPosition = scaledTranslation + mPosition; tr.setOrigin(toBullet(newPosition)); mCollisionObject->setWorldTransform(tr); } -osg::Vec3f Actor::getPosition() const +osg::Vec3f Actor::getCollisionObjectPosition() const { return toOsg(mCollisionObject->getWorldTransform().getOrigin()); } +void Actor::setPosition(const osg::Vec3f &position) +{ + mPreviousPosition = mPosition; + + mPosition = position; + updateCollisionObjectPosition(); +} + +osg::Vec3f Actor::getPosition() const +{ + return mPosition; +} + +osg::Vec3f Actor::getPreviousPosition() const +{ + return mPreviousPosition; +} + void Actor::updateRotation () { btTransform tr = mCollisionObject->getWorldTransform(); @@ -106,7 +130,7 @@ void Actor::updateRotation () tr.setRotation(toBullet(mRotation)); mCollisionObject->setWorldTransform(tr); - updatePosition(); + updateCollisionObjectPosition(); } void Actor::updateScale() @@ -122,7 +146,7 @@ void Actor::updateScale() mPtr.getClass().adjustScale(mPtr, scaleVec, true); mRenderingScale = scaleVec; - updatePosition(); + updateCollisionObjectPosition(); } osg::Vec3f Actor::getHalfExtents() const diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 03193c675e..b238547e11 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -15,7 +15,7 @@ class btCollisionObject; namespace Resource { - class BulletShapeInstance; + class BulletShape; } namespace MWPhysics @@ -48,7 +48,7 @@ namespace MWPhysics class Actor : public PtrHolder { public: - Actor(const MWWorld::Ptr& ptr, osg::ref_ptr shape, btCollisionWorld* world); + Actor(const MWWorld::Ptr& ptr, osg::ref_ptr shape, btCollisionWorld* world); ~Actor(); /** @@ -68,8 +68,15 @@ namespace MWPhysics void updateScale(); void updateRotation(); + + /** + * Set mPosition and mPreviousPosition to the position in the Ptr's RefData. This should be used + * when an object is "instantly" moved/teleported as opposed to being moved by the physics simulation. + */ void updatePosition(); + void updateCollisionObjectPosition(); + /** * Returns the half extents of the collision body (scaled according to collision scale) */ @@ -79,8 +86,17 @@ namespace MWPhysics * Returns the position of the collision body * @note The collision shape's origin is in its center, so the position returned can be described as center of the actor collision box in world space. */ + osg::Vec3f getCollisionObjectPosition() const; + + /** + * Store the current position into mPreviousPosition, then move to this position. + */ + void setPosition(const osg::Vec3f& position); + osg::Vec3f getPosition() const; + osg::Vec3f getPreviousPosition() const; + /** * Returns the half extents of the collision body (scaled according to rendering scale) * @note The reason we need this extra method is because of an inconsistency in MW - NPC race scales aren't applied to the collision shape, @@ -138,6 +154,7 @@ namespace MWPhysics osg::Vec3f mScale; osg::Vec3f mRenderingScale; osg::Vec3f mPosition; + osg::Vec3f mPreviousPosition; osg::Vec3f mForce; bool mOnGround; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index f6883ae35d..95de8430d6 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -22,6 +22,7 @@ #include #include +#include #include // FindRecIndexVisitor @@ -67,8 +68,8 @@ namespace MWPhysics return osg::RadiansToDegrees(std::acos(normal * osg::Vec3f(0.f, 0.f, 1.f))); } - static bool stepMove(btCollisionObject *colobj, osg::Vec3f &position, - const osg::Vec3f &toMove, float &remainingTime, btCollisionWorld* collisionWorld) + static bool stepMove(const btCollisionObject *colobj, osg::Vec3f &position, + const osg::Vec3f &toMove, float &remainingTime, const btCollisionWorld* collisionWorld) { /* * Slide up an incline or set of stairs. Should be called only after a @@ -220,7 +221,7 @@ namespace MWPhysics collisionWorld->rayTest(from, to, resultCallback1); if (resultCallback1.hasHit() && - ( (toOsg(resultCallback1.m_hitPointWorld) - tracer.mEndPos).length() > 30 + ( (toOsg(resultCallback1.m_hitPointWorld) - tracer.mEndPos).length() > 35 || getSlope(tracer.mPlaneNormal) > sMaxSlope)) { actor->setOnGround(getSlope(toOsg(resultCallback1.m_hitNormalWorld)) <= sMaxSlope); @@ -233,13 +234,11 @@ namespace MWPhysics } } - static osg::Vec3f move(const MWWorld::Ptr &ptr, Actor* physicActor, const osg::Vec3f &movement, float time, - bool isFlying, float waterlevel, float slowFall, btCollisionWorld* collisionWorld, + static osg::Vec3f move(osg::Vec3f position, const MWWorld::Ptr &ptr, Actor* physicActor, const osg::Vec3f &movement, float time, + bool isFlying, float waterlevel, float slowFall, const btCollisionWorld* collisionWorld, std::map& standingCollisionTracker) { const ESM::Position& refpos = ptr.getRefData().getPosition(); - osg::Vec3f position(refpos.asVec3()); - // Early-out for totally static creatures // (Not sure if gravity should still apply?) if (!ptr.getClass().isMobile(ptr)) @@ -255,7 +254,7 @@ namespace MWPhysics ) * movement * time; } - btCollisionObject *colobj = physicActor->getCollisionObject(); + const btCollisionObject *colobj = physicActor->getCollisionObject(); osg::Vec3f halfExtents = physicActor->getHalfExtents(); // NOTE: here we don't account for the collision box translation (i.e. physicActor->getPosition() - refpos.pos). @@ -281,16 +280,16 @@ namespace MWPhysics { velocity = (osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * movement; - if (velocity.z() > 0.f) + if (velocity.z() > 0.f && physicActor->getOnGround()) inertia = velocity; - if(!physicActor->getOnGround()) + else if(!physicActor->getOnGround()) { velocity = velocity + physicActor->getInertialForce(); } } - // dead actors underwater will float to the surface - if (ptr.getClass().getCreatureStats(ptr).isDead() && position.z() < swimlevel) + // dead actors underwater will float to the surface, if the CharacterController tells us to do so + if (movement.z() > 0 && ptr.getClass().getCreatureStats(ptr).isDead() && position.z() < swimlevel) velocity = osg::Vec3f(0,0,1) * 25; ptr.getClass().getMovementSettings(ptr).mPosition[2] = 0; @@ -449,6 +448,10 @@ namespace MWPhysics inertia.z() += time * -627.2f; if (inertia.z() < 0) inertia.z() *= slowFall; + if (slowFall < 1.f) { + inertia.x() = 0; + inertia.y() = 0; + } physicActor->setInertialForce(inertia); } physicActor->setOnGround(isOnGround); @@ -506,6 +509,9 @@ namespace MWPhysics private: btHeightfieldTerrainShape* mShape; btCollisionObject* mCollisionObject; + + void operator=(const HeightField&); + HeightField(const HeightField&); }; // -------------------------------------------------------------- @@ -530,6 +536,11 @@ namespace MWPhysics setOrigin(btVector3(pos[0], pos[1], pos[2])); } + const Resource::BulletShapeInstance* getShapeInstance() const + { + return mShapeInstance.get(); + } + void setScale(float scale) { mShapeInstance->getCollisionShape()->setLocalScaling(btVector3(scale,scale,scale)); @@ -550,6 +561,11 @@ namespace MWPhysics return mCollisionObject.get(); } + const btCollisionObject* getCollisionObject() const + { + return mCollisionObject.get(); + } + /// Return solid flag. Not used by the object itself, true by default. bool isSolid() const { @@ -573,7 +589,7 @@ namespace MWPhysics assert (mShapeInstance->getCollisionShape()->isCompound()); - btCompoundShape* compound = dynamic_cast(mShapeInstance->getCollisionShape()); + btCompoundShape* compound = static_cast(mShapeInstance->getCollisionShape()); for (std::map::iterator it = mShapeInstance->mAnimatedShapes.begin(); it != mShapeInstance->mAnimatedShapes.end();) { @@ -637,12 +653,15 @@ namespace MWPhysics PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr parentNode) : mShapeManager(new Resource::BulletShapeManager(resourceSystem->getVFS(), resourceSystem->getSceneManager(), resourceSystem->getNifFileManager())) + , mResourceSystem(resourceSystem) , mDebugDrawEnabled(false) , mTimeAccum(0.0f) , mWaterHeight(0) , mWaterEnabled(false) , mParentNode(parentNode) { + mResourceSystem->addResourceManager(mShapeManager.get()); + mCollisionConfiguration = new btDefaultCollisionConfiguration(); mDispatcher = new btCollisionDispatcher(mCollisionConfiguration); mBroadphase = new btDbvtBroadphase(); @@ -656,6 +675,8 @@ namespace MWPhysics PhysicsSystem::~PhysicsSystem() { + mResourceSystem->removeResourceManager(mShapeManager.get()); + if (mWaterCollisionObject.get()) mCollisionWorld->removeCollisionObject(mWaterCollisionObject.get()); @@ -682,6 +703,16 @@ namespace MWPhysics delete mBroadphase; } + void PhysicsSystem::setUnrefQueue(SceneUtil::UnrefQueue *unrefQueue) + { + mUnrefQueue = unrefQueue; + } + + Resource::BulletShapeManager *PhysicsSystem::getShapeManager() + { + return mShapeManager.get(); + } + bool PhysicsSystem::toggleDebugRendering() { mDebugDrawEnabled = !mDebugDrawEnabled; @@ -743,18 +774,11 @@ namespace MWPhysics mLeastDistSqr(std::numeric_limits::max()) { } -#if BT_BULLET_VERSION >= 281 virtual btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* col0Wrap,int partId0,int index0, const btCollisionObjectWrapper* col1Wrap,int partId1,int index1) { const btCollisionObject* collisionObject = col1Wrap->m_collisionObject; -#else - virtual btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObject* col0, int partId0, int index0, - const btCollisionObject* col1, int partId1, int index1) - { - const btCollisionObject* collisionObject = col1; -#endif if (collisionObject != mMe) { btScalar distsqr = mOrigin.distance2(cp.getPositionWorldOnA()); @@ -868,6 +892,12 @@ namespace MWPhysics const Actor* actor = getActor(ignore); if (actor) me = actor->getCollisionObject(); + else + { + const Object* object = getObject(ignore); + if (object) + me = object->getCollisionObject(); + } } ClosestNotMeRayResultCallback resultCallback(me, btFrom, btTo); @@ -920,8 +950,8 @@ namespace MWPhysics if (!physactor1 || !physactor2) return false; - osg::Vec3f pos1 (physactor1->getPosition() + osg::Vec3f(0,0,physactor1->getHalfExtents().z() * 0.8)); // eye level - osg::Vec3f pos2 (physactor2->getPosition() + osg::Vec3f(0,0,physactor2->getHalfExtents().z() * 0.8)); + osg::Vec3f pos1 (physactor1->getCollisionObjectPosition() + osg::Vec3f(0,0,physactor1->getHalfExtents().z() * 0.9)); // eye level + osg::Vec3f pos2 (physactor2->getCollisionObjectPosition() + osg::Vec3f(0,0,physactor2->getHalfExtents().z() * 0.9)); RayResult result = castRay(pos1, pos2, MWWorld::Ptr(), CollisionType_World|CollisionType_HeightMap|CollisionType_Door); @@ -983,11 +1013,11 @@ namespace MWPhysics return osg::Vec3f(); } - osg::Vec3f PhysicsSystem::getPosition(const MWWorld::ConstPtr &actor) const + osg::Vec3f PhysicsSystem::getCollisionObjectPosition(const MWWorld::ConstPtr &actor) const { const Actor* physactor = getActor(actor); if (physactor) - return physactor->getPosition(); + return physactor->getCollisionObjectPosition(); else return osg::Vec3f(); } @@ -1004,7 +1034,6 @@ namespace MWPhysics std::vector mResult; -#if BT_BULLET_VERSION >= 281 virtual btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* col0Wrap,int partId0,int index0, const btCollisionObjectWrapper* col1Wrap,int partId1,int index1) @@ -1012,14 +1041,6 @@ namespace MWPhysics const btCollisionObject* collisionObject = col0Wrap->m_collisionObject; if (collisionObject == mTestedAgainst) collisionObject = col1Wrap->m_collisionObject; -#else - virtual btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObject* col0, int partId0, int index0, - const btCollisionObject* col1, int partId1, int index1) - { - const btCollisionObject* collisionObject = col0; - if (collisionObject == mTestedAgainst) - collisionObject = col1; -#endif PtrHolder* holder = static_cast(collisionObject->getUserPointer()); if (holder) mResult.push_back(holder->getPtr()); @@ -1075,7 +1096,7 @@ namespace MWPhysics void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType) { - osg::ref_ptr shapeInstance = mShapeManager->createInstance(mesh); + osg::ref_ptr shapeInstance = mShapeManager->getInstance(mesh); if (!shapeInstance || !shapeInstance->getCollisionShape()) return; @@ -1096,6 +1117,9 @@ namespace MWPhysics { mCollisionWorld->removeCollisionObject(found->second->getCollisionObject()); + if (mUnrefQueue.get()) + mUnrefQueue->push(found->second->getShapeInstance()); + mAnimatedObjects.erase(found->second); delete found->second; @@ -1165,6 +1189,14 @@ namespace MWPhysics return NULL; } + const Object* PhysicsSystem::getObject(const MWWorld::ConstPtr &ptr) const + { + ObjectMap::const_iterator found = mObjects.find(ptr); + if (found != mObjects.end()) + return found->second; + return NULL; + } + void PhysicsSystem::updateScale(const MWWorld::Ptr &ptr) { ObjectMap::iterator found = mObjects.find(ptr); @@ -1221,11 +1253,11 @@ namespace MWPhysics } void PhysicsSystem::addActor (const MWWorld::Ptr& ptr, const std::string& mesh) { - osg::ref_ptr shapeInstance = mShapeManager->createInstance(mesh); - if (!shapeInstance) + osg::ref_ptr shape = mShapeManager->getShape(mesh); + if (!shape) return; - Actor* actor = new Actor(ptr, shapeInstance, mCollisionWorld); + Actor* actor = new Actor(ptr, shape, mCollisionWorld); mActors.insert(std::make_pair(ptr, actor)); } @@ -1269,54 +1301,69 @@ namespace MWPhysics mMovementResults.clear(); mTimeAccum += dt; - if(mTimeAccum >= 1.0f/60.0f) + const float physicsDt = 1.f/60.0f; + + const int maxAllowedSteps = 20; + int numSteps = mTimeAccum / (physicsDt); + numSteps = std::min(numSteps, maxAllowedSteps); + + mTimeAccum -= numSteps * physicsDt; + + if (numSteps) { // Collision events should be available on every frame mStandingCollisions.clear(); + } - const MWBase::World *world = MWBase::Environment::get().getWorld(); - PtrVelocityList::iterator iter = mMovementQueue.begin(); - for(;iter != mMovementQueue.end();++iter) + const MWBase::World *world = MWBase::Environment::get().getWorld(); + PtrVelocityList::iterator iter = mMovementQueue.begin(); + for(;iter != mMovementQueue.end();++iter) + { + float waterlevel = -std::numeric_limits::max(); + const MWWorld::CellStore *cell = iter->first.getCell(); + if(cell->getCell()->hasWater()) + waterlevel = cell->getWaterLevel(); + + + const MWMechanics::MagicEffects& effects = iter->first.getClass().getCreatureStats(iter->first).getMagicEffects(); + + bool waterCollision = false; + if (effects.get(ESM::MagicEffect::WaterWalking).getMagnitude() + && cell->getCell()->hasWater() + && !world->isUnderwater(iter->first.getCell(), + osg::Vec3f(iter->first.getRefData().getPosition().asVec3()))) + waterCollision = true; + + ActorMap::iterator foundActor = mActors.find(iter->first); + if (foundActor == mActors.end()) // actor was already removed from the scene + continue; + Actor* physicActor = foundActor->second; + physicActor->setCanWaterWalk(waterCollision); + + // Slow fall reduces fall speed by a factor of (effect magnitude / 200) + float slowFall = 1.f - std::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f)); + + osg::Vec3f position = physicActor->getPosition(); + float oldHeight = position.z(); + for (int i=0; i::max(); - const MWWorld::CellStore *cell = iter->first.getCell(); - if(cell->getCell()->hasWater()) - waterlevel = cell->getWaterLevel(); - - float oldHeight = iter->first.getRefData().getPosition().pos[2]; - - const MWMechanics::MagicEffects& effects = iter->first.getClass().getCreatureStats(iter->first).getMagicEffects(); - - bool waterCollision = false; - if (effects.get(ESM::MagicEffect::WaterWalking).getMagnitude() - && cell->getCell()->hasWater() - && !world->isUnderwater(iter->first.getCell(), - osg::Vec3f(iter->first.getRefData().getPosition().asVec3()))) - waterCollision = true; - - ActorMap::iterator foundActor = mActors.find(iter->first); - if (foundActor == mActors.end()) // actor was already removed from the scene - continue; - Actor* physicActor = foundActor->second; - physicActor->setCanWaterWalk(waterCollision); - - // Slow fall reduces fall speed by a factor of (effect magnitude / 200) - float slowFall = 1.f - std::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f)); - - osg::Vec3f newpos = MovementSolver::move(physicActor->getPtr(), physicActor, iter->second, mTimeAccum, - world->isFlying(iter->first), - waterlevel, slowFall, mCollisionWorld, mStandingCollisions); - - float heightDiff = newpos.z() - oldHeight; - - if (heightDiff < 0) - iter->first.getClass().getCreatureStats(iter->first).addToFallHeight(-heightDiff); - - mMovementResults.push_back(std::make_pair(iter->first, newpos)); + position = MovementSolver::move(position, physicActor->getPtr(), physicActor, iter->second, physicsDt, + world->isFlying(iter->first), + waterlevel, slowFall, mCollisionWorld, mStandingCollisions); + physicActor->setPosition(position); } - mTimeAccum = 0.0f; + float interpolationFactor = mTimeAccum / physicsDt; + osg::Vec3f interpolated = position * interpolationFactor + physicActor->getPreviousPosition() * (1.f - interpolationFactor); + + float heightDiff = position.z() - oldHeight; + + if (heightDiff < 0) + iter->first.getClass().getCreatureStats(iter->first).addToFallHeight(-heightDiff); + + mMovementResults.push_back(std::make_pair(iter->first, interpolated)); } + mMovementQueue.clear(); return mMovementResults; diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index f53d7e3d9b..c12672285e 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -25,11 +25,12 @@ namespace MWRender namespace Resource { class BulletShapeManager; + class ResourceSystem; } -namespace Resource +namespace SceneUtil { - class ResourceSystem; + class UnrefQueue; } class btCollisionWorld; @@ -53,6 +54,10 @@ namespace MWPhysics PhysicsSystem (Resource::ResourceSystem* resourceSystem, osg::ref_ptr parentNode); ~PhysicsSystem (); + void setUnrefQueue(SceneUtil::UnrefQueue* unrefQueue); + + Resource::BulletShapeManager* getShapeManager(); + void enableWater(float height); void setWaterHeight(float height); void disableWater(); @@ -65,6 +70,8 @@ namespace MWPhysics Actor* getActor(const MWWorld::Ptr& ptr); const Actor* getActor(const MWWorld::ConstPtr& ptr) const; + const Object* getObject(const MWWorld::ConstPtr& ptr) const; + // Object or Actor void remove (const MWWorld::Ptr& ptr); @@ -124,7 +131,7 @@ namespace MWPhysics /// Get the position of the collision shape for the actor. Use together with getHalfExtents() to get the collision bounds in world space. /// @note The collision shape's origin is in its center, so the position returned can be described as center of the actor collision box in world space. - osg::Vec3f getPosition(const MWWorld::ConstPtr& actor) const; + osg::Vec3f getCollisionObjectPosition(const MWWorld::ConstPtr& actor) const; /// Queues velocity movement for a Ptr. If a Ptr is already queued, its velocity will /// be overwritten. Valid until the next call to applyQueuedMovement. @@ -163,12 +170,15 @@ namespace MWPhysics void updateWater(); + osg::ref_ptr mUnrefQueue; + btBroadphaseInterface* mBroadphase; btDefaultCollisionConfiguration* mCollisionConfiguration; btCollisionDispatcher* mDispatcher; btCollisionWorld* mCollisionWorld; std::auto_ptr mShapeManager; + Resource::ResourceSystem* mResourceSystem; typedef std::map ObjectMap; ObjectMap mObjects; diff --git a/apps/openmw/mwphysics/trace.cpp b/apps/openmw/mwphysics/trace.cpp index 94434b856a..420ca1a9e5 100644 --- a/apps/openmw/mwphysics/trace.cpp +++ b/apps/openmw/mwphysics/trace.cpp @@ -16,7 +16,7 @@ namespace MWPhysics class ClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback { public: - ClosestNotMeConvexResultCallback(btCollisionObject *me, const btVector3 &up, btScalar minSlopeDot) + ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &up, btScalar minSlopeDot) : btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)), mMe(me), mUp(up), mMinSlopeDot(minSlopeDot) { @@ -44,13 +44,13 @@ public: } protected: - btCollisionObject *mMe; + const btCollisionObject *mMe; const btVector3 mUp; const btScalar mMinSlopeDot; }; -void ActorTracer::doTrace(btCollisionObject *actor, const osg::Vec3f& start, const osg::Vec3f& end, btCollisionWorld* world) +void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world) { const btVector3 btstart = toBullet(start); const btVector3 btend = toBullet(end); @@ -66,9 +66,9 @@ void ActorTracer::doTrace(btCollisionObject *actor, const osg::Vec3f& start, con newTraceCallback.m_collisionFilterGroup = actor->getBroadphaseHandle()->m_collisionFilterGroup; newTraceCallback.m_collisionFilterMask = actor->getBroadphaseHandle()->m_collisionFilterMask; - btCollisionShape *shape = actor->getCollisionShape(); + const btCollisionShape *shape = actor->getCollisionShape(); assert(shape->isConvex()); - world->convexSweepTest(static_cast(shape), + world->convexSweepTest(static_cast(shape), from, to, newTraceCallback); // Copy the hit data over to our trace results struct: @@ -89,7 +89,7 @@ void ActorTracer::doTrace(btCollisionObject *actor, const osg::Vec3f& start, con } } -void ActorTracer::findGround(const Actor* actor, const osg::Vec3f& start, const osg::Vec3f& end, btCollisionWorld* world) +void ActorTracer::findGround(const Actor* actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world) { const btVector3 btstart(start.x(), start.y(), start.z()+1.0f); const btVector3 btend(end.x(), end.y(), end.z()+1.0f); diff --git a/apps/openmw/mwphysics/trace.h b/apps/openmw/mwphysics/trace.h index ef1a24d44a..7b7d0391e6 100644 --- a/apps/openmw/mwphysics/trace.h +++ b/apps/openmw/mwphysics/trace.h @@ -19,8 +19,8 @@ namespace MWPhysics float mFraction; - void doTrace(btCollisionObject *actor, const osg::Vec3f& start, const osg::Vec3f& end, btCollisionWorld* world); - void findGround(const Actor* actor, const osg::Vec3f& start, const osg::Vec3f& end, btCollisionWorld* world); + void doTrace(const btCollisionObject *actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world); + void findGround(const Actor* actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world); }; } diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 68a073731d..ddff61ac7a 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -3,24 +3,21 @@ #include #include -#include #include #include -#include #include -#include #include #include -#include #include +#include #include #include #include #include -#include +#include #include // KeyframeHolder #include @@ -30,16 +27,16 @@ #include #include #include -#include -#include +#include #include #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/fallback.hpp" #include "../mwworld/cellstore.hpp" #include "../mwmechanics/character.hpp" // FIXME: for MWMechanics::Priority @@ -51,11 +48,49 @@ namespace { + /// Removes all particle systems and related nodes in a subgraph. + class RemoveParticlesVisitor : public osg::NodeVisitor + { + public: + RemoveParticlesVisitor() + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + { } + + virtual void apply(osg::Node &node) + { + if (dynamic_cast(&node)) + mToRemove.push_back(&node); + + traverse(node); + } + + virtual void apply(osg::Drawable& drw) + { + if (osgParticle::ParticleSystem* partsys = dynamic_cast(&drw)) + mToRemove.push_back(partsys); + } + + void remove() + { + for (std::vector >::iterator it = mToRemove.begin(); it != mToRemove.end(); ++it) + { + // FIXME: a Drawable might have more than one parent + osg::Node* node = *it; + if (node->getNumParents()) + node->getParent(0)->removeChild(node); + } + mToRemove.clear(); + } + + private: + std::vector > mToRemove; + }; + class GlowUpdater : public SceneUtil::StateSetUpdater { public: - GlowUpdater(osg::Vec4f color, const std::vector >& textures) - : mTexUnit(1) // FIXME: might not always be 1 + GlowUpdater(int texUnit, osg::Vec4f color, const std::vector >& textures) + : mTexUnit(texUnit) , mColor(color) , mTextures(textures) { @@ -96,7 +131,12 @@ namespace class NodeMapVisitor : public osg::NodeVisitor { public: - NodeMapVisitor() : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) {} + typedef std::map > NodeMap; + + NodeMapVisitor(NodeMap& map) + : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) + , mMap(map) + {} void apply(osg::MatrixTransform& trans) { @@ -104,15 +144,8 @@ namespace traverse(trans); } - typedef std::map > NodeMap; - - const NodeMap& getNodeMap() const - { - return mMap; - } - private: - NodeMap mMap; + NodeMap& mMap; }; NifOsg::TextKeyMap::const_iterator findGroupStart(const NifOsg::TextKeyMap &keys, const std::string &groupname) @@ -204,17 +237,10 @@ namespace class RemoveDrawableVisitor : public RemoveVisitor { public: - virtual void apply(osg::Geode &geode) - { - applyImpl(geode); - } - -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) virtual void apply(osg::Drawable& drw) { applyImpl(drw); } -#endif void applyImpl(osg::Node& node) { @@ -242,17 +268,10 @@ namespace class RemoveTriBipVisitor : public RemoveVisitor { public: - virtual void apply(osg::Geode &node) - { - applyImpl(node); - } - -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) virtual void apply(osg::Drawable& drw) { applyImpl(drw); } -#endif void applyImpl(osg::Node& node) { @@ -279,7 +298,7 @@ namespace MWRender ControllerMap mControllerMap[Animation::sNumBlendMasks]; - const std::multimap& getTextKeys(); + const std::multimap& getTextKeys() const; }; class ResetAccumRootCallback : public osg::NodeCallback @@ -313,6 +332,7 @@ namespace MWRender Animation::Animation(const MWWorld::Ptr &ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem) : mInsert(parentNode) , mSkeleton(NULL) + , mNodeMapCreated(false) , mPtr(ptr) , mResourceSystem(resourceSystem) , mAccumulate(1.f, 1.f, 0.f) @@ -333,7 +353,7 @@ namespace MWRender mInsert->removeChild(mObjectRoot); } - MWWorld::Ptr Animation::getPtr() + MWWorld::ConstPtr Animation::getPtr() const { return mPtr; } @@ -357,7 +377,7 @@ namespace MWRender mResetAccumRootCallback->setAccumulate(mAccumulate); } - size_t Animation::detectBlendMask(osg::Node* node) + size_t Animation::detectBlendMask(const osg::Node* node) const { static const char sBlendMaskRoots[sNumBlendMasks][32] = { "", /* Lower body / character root */ @@ -383,7 +403,7 @@ namespace MWRender return 0; } - const std::multimap &Animation::AnimSource::getTextKeys() + const std::multimap &Animation::AnimSource::getTextKeys() const { return mKeyframes->mTextKeys; } @@ -408,12 +428,14 @@ namespace MWRender if (!animsrc->mKeyframes || animsrc->mKeyframes->mTextKeys.empty() || animsrc->mKeyframes->mKeyframeControllers.empty()) return; + const NodeMap& nodeMap = getNodeMap(); + for (NifOsg::KeyframeHolder::KeyframeControllerMap::const_iterator it = animsrc->mKeyframes->mKeyframeControllers.begin(); it != animsrc->mKeyframes->mKeyframeControllers.end(); ++it) { std::string bonename = Misc::StringUtils::lowerCase(it->first); - NodeMap::const_iterator found = mNodeMap.find(bonename); - if (found == mNodeMap.end()) + NodeMap::const_iterator found = nodeMap.find(bonename); + if (found == nodeMap.end()) { std::cerr << "addAnimSource: can't find bone '" + bonename << "' in " << model << " (referenced by " << kfname << ")" << std::endl; continue; @@ -437,11 +459,11 @@ namespace MWRender if (!mAccumRoot) { - NodeMap::const_iterator found = mNodeMap.find("root bone"); - if (found == mNodeMap.end()) - found = mNodeMap.find("bip01"); + NodeMap::const_iterator found = nodeMap.find("root bone"); + if (found == nodeMap.end()) + found = nodeMap.find("bip01"); - if (found != mNodeMap.end()) + if (found != nodeMap.end()) mAccumRoot = found->second; } } @@ -458,7 +480,7 @@ namespace MWRender mAnimSources.clear(); } - bool Animation::hasAnimation(const std::string &anim) + bool Animation::hasAnimation(const std::string &anim) const { AnimSourceList::const_iterator iter(mAnimSources.begin()); for(;iter != mAnimSources.end();++iter) @@ -691,6 +713,17 @@ namespace MWRender mTextKeyListener = listener; } + const Animation::NodeMap &Animation::getNodeMap() const + { + if (!mNodeMapCreated && mObjectRoot) + { + NodeMapVisitor visitor(mNodeMap); + mObjectRoot->accept(visitor); + mNodeMapCreated = true; + } + return mNodeMap; + } + void Animation::resetActiveGroups() { // remove all previous external controllers from the scene graph @@ -730,7 +763,7 @@ namespace MWRender for (AnimSource::ControllerMap::iterator it = animsrc->mControllerMap[blendMask].begin(); it != animsrc->mControllerMap[blendMask].end(); ++it) { - osg::ref_ptr node = mNodeMap.at(it->first); // this should not throw, we already checked for the node existing in addAnimSource + osg::ref_ptr node = getNodeMap().at(it->first); // this should not throw, we already checked for the node existing in addAnimSource node->addUpdateCallback(it->second); mActiveControllers.insert(std::make_pair(node, it->second)); @@ -979,13 +1012,14 @@ namespace MWRender mSkeleton = NULL; mNodeMap.clear(); + mNodeMapCreated = false; mActiveControllers.clear(); mAccumRoot = NULL; mAccumCtrl = NULL; if (!forceskeleton) { - osg::ref_ptr created = mResourceSystem->getSceneManager()->createInstance(model, mInsert); + osg::ref_ptr created = mResourceSystem->getSceneManager()->getInstance(model, mInsert); mObjectRoot = created->asGroup(); if (!mObjectRoot) { @@ -997,7 +1031,7 @@ namespace MWRender } else { - osg::ref_ptr created = mResourceSystem->getSceneManager()->createInstance(model); + osg::ref_ptr created = mResourceSystem->getSceneManager()->getInstance(model); osg::ref_ptr skel = dynamic_cast(created.get()); if (!skel) { @@ -1026,10 +1060,6 @@ namespace MWRender removeTriBipVisitor.remove(); } - NodeMapVisitor visitor; - mObjectRoot->accept(visitor); - mNodeMap = visitor.getNodeMap(); - mObjectRoot->addCullCallback(new SceneUtil::LightListCallback); } @@ -1048,6 +1078,25 @@ namespace MWRender return mObjectRoot.get(); } + class FindLowestUnusedTexUnitVisitor : public osg::NodeVisitor + { + public: + FindLowestUnusedTexUnitVisitor() + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mLowestUnusedTexUnit(0) + { + } + + virtual void apply(osg::Node& node) + { + if (osg::StateSet* stateset = node.getStateSet()) + mLowestUnusedTexUnit = std::max(mLowestUnusedTexUnit, int(stateset->getTextureAttributeList().size())); + + traverse(node); + } + int mLowestUnusedTexUnit; + }; + void Animation::addGlow(osg::ref_ptr node, osg::Vec4f glowColor) { std::vector > textures; @@ -1060,15 +1109,35 @@ namespace MWRender stream << i; stream << ".dds"; - textures.push_back(mResourceSystem->getTextureManager()->getTexture2D(stream.str(), osg::Texture2D::REPEAT, osg::Texture2D::REPEAT)); + osg::ref_ptr image = mResourceSystem->getImageManager()->getImage(stream.str()); + osg::ref_ptr tex (new osg::Texture2D(image)); + tex->setName("envMap"); + tex->setWrap(osg::Texture::WRAP_S, osg::Texture2D::REPEAT); + tex->setWrap(osg::Texture::WRAP_T, osg::Texture2D::REPEAT); + mResourceSystem->getSceneManager()->applyFilterSettings(tex); + textures.push_back(tex); } - osg::ref_ptr glowupdater (new GlowUpdater(glowColor, textures)); + FindLowestUnusedTexUnitVisitor findLowestUnusedTexUnitVisitor; + node->accept(findLowestUnusedTexUnitVisitor); + int texUnit = findLowestUnusedTexUnitVisitor.mLowestUnusedTexUnit; + osg::ref_ptr glowupdater (new GlowUpdater(texUnit, glowColor, textures)); node->addUpdateCallback(glowupdater); + + // set a texture now so that the ShaderVisitor can find it + osg::ref_ptr writableStateSet = NULL; + if (!node->getStateSet()) + writableStateSet = node->getOrCreateStateSet(); + else + writableStateSet = osg::clone(node->getStateSet(), osg::CopyOp::SHALLOW_COPY); + writableStateSet->setTextureAttributeAndModes(texUnit, textures.front(), osg::StateAttribute::ON); + writableStateSet->addUniform(new osg::Uniform("envMapColor", glowColor)); + + mResourceSystem->getSceneManager()->recreateShaders(node); } // TODO: Should not be here - osg::Vec4f Animation::getEnchantmentColor(MWWorld::Ptr item) + osg::Vec4f Animation::getEnchantmentColor(const MWWorld::ConstPtr& item) const { osg::Vec4f result(1,1,1,1); std::string enchantmentName = item.getClass().getEnchantment(item); @@ -1086,38 +1155,7 @@ namespace MWRender void Animation::addExtraLight(osg::ref_ptr parent, const ESM::Light *esmLight) { - SceneUtil::FindByNameVisitor visitor("AttachLight"); - parent->accept(visitor); - - osg::Group* attachTo = NULL; - if (visitor.mFoundNode) - { - attachTo = visitor.mFoundNode; - } - else - { - osg::ComputeBoundsVisitor computeBound; - computeBound.setTraversalMask(~Mask_ParticleSystem); - parent->accept(computeBound); - - // PositionAttitudeTransform seems to be slightly faster than MatrixTransform - osg::ref_ptr trans(new osg::PositionAttitudeTransform); - trans->setPosition(computeBound.getBoundingBox().center()); - - parent->addChild(trans); - - attachTo = trans; - } - - osg::ref_ptr lightSource = new SceneUtil::LightSource; - osg::ref_ptr light (new osg::Light); - lightSource->setNodeMask(Mask_Lighting); - - const MWWorld::Fallback* fallback = MWBase::Environment::get().getWorld()->getFallback(); - - float radius = esmLight->mData.mRadius; - lightSource->setRadius(radius); - + const Fallback::Map* fallback = MWBase::Environment::get().getWorld()->getFallback(); static bool outQuadInLin = fallback->getFallbackBool("LightAttenuation_OutQuadInLin"); static bool useQuadratic = fallback->getFallbackBool("LightAttenuation_UseQuadratic"); static float quadraticValue = fallback->getFallbackFloat("LightAttenuation_QuadraticValue"); @@ -1125,38 +1163,10 @@ namespace MWRender static bool useLinear = fallback->getFallbackBool("LightAttenuation_UseLinear"); static float linearRadiusMult = fallback->getFallbackFloat("LightAttenuation_LinearRadiusMult"); static float linearValue = fallback->getFallbackFloat("LightAttenuation_LinearValue"); - bool exterior = mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior(); - SceneUtil::configureLight(light, radius, exterior, outQuadInLin, useQuadratic, quadraticValue, - quadraticRadiusMult, useLinear, linearRadiusMult, linearValue); - - osg::Vec4f diffuse = SceneUtil::colourFromRGB(esmLight->mData.mColor); - if (esmLight->mData.mFlags & ESM::Light::Negative) - { - diffuse *= -1; - diffuse.a() = 1; - } - light->setDiffuse(diffuse); - light->setAmbient(osg::Vec4f(0,0,0,1)); - light->setSpecular(osg::Vec4f(0,0,0,0)); - - lightSource->setLight(light); - - osg::ref_ptr ctrl (new SceneUtil::LightController); - ctrl->setDiffuse(light->getDiffuse()); - if (esmLight->mData.mFlags & ESM::Light::Flicker) - ctrl->setType(SceneUtil::LightController::LT_Flicker); - if (esmLight->mData.mFlags & ESM::Light::FlickerSlow) - ctrl->setType(SceneUtil::LightController::LT_FlickerSlow); - if (esmLight->mData.mFlags & ESM::Light::Pulse) - ctrl->setType(SceneUtil::LightController::LT_Pulse); - if (esmLight->mData.mFlags & ESM::Light::PulseSlow) - ctrl->setType(SceneUtil::LightController::LT_PulseSlow); - - lightSource->addUpdateCallback(ctrl); - - attachTo->addChild(lightSource); + SceneUtil::addLight(parent, esmLight, Mask_ParticleSystem, Mask_Lighting, exterior, outQuadInLin, + useQuadratic, quadraticValue, quadraticRadiusMult, useLinear, linearRadiusMult, linearValue); } void Animation::addEffect (const std::string& model, int effectId, bool loop, const std::string& bonename, std::string texture) @@ -1176,13 +1186,13 @@ namespace MWRender parentNode = mInsert; else { - NodeMap::iterator found = mNodeMap.find(Misc::StringUtils::lowerCase(bonename)); - if (found == mNodeMap.end()) + NodeMap::const_iterator found = getNodeMap().find(Misc::StringUtils::lowerCase(bonename)); + if (found == getNodeMap().end()) throw std::runtime_error("Can't find bone " + bonename); parentNode = found->second; } - osg::ref_ptr node = mResourceSystem->getSceneManager()->createInstance(model, parentNode); + osg::ref_ptr node = mResourceSystem->getSceneManager()->getInstance(model, parentNode); node->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); @@ -1227,9 +1237,9 @@ namespace MWRender } } - void Animation::getLoopingEffects(std::vector &out) + void Animation::getLoopingEffects(std::vector &out) const { - for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) + for (std::vector::const_iterator it = mEffects.begin(); it != mEffects.end(); ++it) { if (it->mLoop) out.push_back(it->mEffectId); @@ -1277,8 +1287,8 @@ namespace MWRender const osg::Node* Animation::getNode(const std::string &name) const { std::string lowerName = Misc::StringUtils::lowerCase(name); - NodeMap::const_iterator found = mNodeMap.find(lowerName); - if (found == mNodeMap.end()) + NodeMap::const_iterator found = getNodeMap().find(lowerName); + if (found == getNodeMap().end()) return NULL; else return found->second; @@ -1305,10 +1315,14 @@ namespace MWRender stateset->setAttributeAndModes(material, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); mObjectRoot->setStateSet(stateset); + + mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot); } else { mObjectRoot->setStateSet(NULL); + + mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot); } setRenderBin(); @@ -1372,13 +1386,29 @@ namespace MWRender if (mPtr.getClass().isBipedal(mPtr)) { - NodeMap::iterator found = mNodeMap.find("bip01 head"); - if (found != mNodeMap.end() && dynamic_cast(found->second.get())) + NodeMap::const_iterator found = getNodeMap().find("bip01 head"); + if (found != getNodeMap().end()) { - osg::Node* node = found->second; - mHeadController = new RotateController(mObjectRoot.get()); - node->addUpdateCallback(mHeadController); - mActiveControllers.insert(std::make_pair(node, mHeadController)); + osg::MatrixTransform* node = found->second; + + bool foundKeyframeCtrl = false; + osg::Callback* cb = node->getUpdateCallback(); + while (cb) + { + if (dynamic_cast(cb)) + { + foundKeyframeCtrl = true; + break; + } + cb = cb->getNestedCallback(); + } + + if (foundKeyframeCtrl) + { + mHeadController = new RotateController(mObjectRoot.get()); + node->addUpdateCallback(mHeadController); + mActiveControllers.insert(std::make_pair(node, mHeadController)); + } } } } @@ -1448,6 +1478,13 @@ namespace MWRender } if (ptr.getTypeName() == typeid(ESM::Light).name() && allowLight) addExtraLight(getOrCreateObjectRoot(), ptr.get()->mBase); + + if (!allowLight && mObjectRoot) + { + RemoveParticlesVisitor visitor; + mObjectRoot->accept(visitor); + visitor.remove(); + } } Animation::AnimState::~AnimState() @@ -1464,8 +1501,20 @@ namespace MWRender PartHolder::~PartHolder() { - if (mNode->getNumParents()) + if (mNode.get() && !mNode->getNumParents()) + std::cerr << "Warning: part has no parents " << std::endl; + + if (mNode.get() && mNode->getNumParents()) + { + if (mNode->getNumParents() > 1) + std::cerr << "Warning: part has multiple parents " << mNode->getNumParents() << " " << mNode.get() << std::endl; mNode->getParent(0)->removeChild(mNode); + } + } + + void PartHolder::unlink() + { + mNode = NULL; } } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 213e33f673..e9d79ced1f 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -55,6 +55,9 @@ public: ~PartHolder(); + /// Unreferences mNode *without* detaching it from the graph. Only use if you know what you are doing. + void unlink(); + osg::ref_ptr getNode() { return mNode; @@ -229,7 +232,8 @@ protected: // Stored in all lowercase for a case-insensitive lookup typedef std::map > NodeMap; - NodeMap mNodeMap; + mutable NodeMap mNodeMap; + mutable bool mNodeMapCreated; MWWorld::Ptr mPtr; @@ -260,11 +264,13 @@ protected: float mAlpha; + const NodeMap& getNodeMap() const; + /* Sets the appropriate animations on the bone groups based on priority. */ void resetActiveGroups(); - size_t detectBlendMask(osg::Node* node); + size_t detectBlendMask(const osg::Node* node) const; /* Updates the position of the accum root node for the given time, and * returns the wanted movement vector from the previous time. */ @@ -292,9 +298,10 @@ protected: */ void setObjectRoot(const std::string &model, bool forceskeleton, bool baseonly, bool isCreature); - /* Adds the keyframe controllers in the specified model as a new animation source. Note that - * the filename portion of the provided model name will be prepended with 'x', and the .nif - * extension will be replaced with .kf. */ + /** Adds the keyframe controllers in the specified model as a new animation source. Note that the .nif + * file extension will be replaced with .kf. + * @note Later added animation sources have the highest priority when it comes to finding a particular animation. + */ void addAnimSource(const std::string &model); /** Adds an additional light to the given node using the specified ESM record. */ @@ -308,7 +315,7 @@ protected: */ virtual void addControllers(); - osg::Vec4f getEnchantmentColor(MWWorld::Ptr item); + osg::Vec4f getEnchantmentColor(const MWWorld::ConstPtr& item) const; void addGlow(osg::ref_ptr node, osg::Vec4f glowColor); @@ -320,7 +327,7 @@ public: Animation(const MWWorld::Ptr &ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem); virtual ~Animation(); - MWWorld::Ptr getPtr(); + MWWorld::ConstPtr getPtr() const; /// Set active flag on the object skeleton, if one exists. /// @see SceneUtil::Skeleton::setActive @@ -342,11 +349,11 @@ public: */ void addEffect (const std::string& model, int effectId, bool loop = false, const std::string& bonename = "", std::string texture = ""); void removeEffect (int effectId); - void getLoopingEffects (std::vector& out); + void getLoopingEffects (std::vector& out) const; virtual void updatePtr(const MWWorld::Ptr &ptr); - bool hasAnimation(const std::string &anim); + bool hasAnimation(const std::string &anim) const; // Specifies the axis' to accumulate on. Non-accumulated axis will just // move visually, but not affect the actual movement. Each x/y/z value diff --git a/apps/openmw/mwrender/bulletdebugdraw.cpp b/apps/openmw/mwrender/bulletdebugdraw.cpp index eaf36cf857..d35c3ac5de 100644 --- a/apps/openmw/mwrender/bulletdebugdraw.cpp +++ b/apps/openmw/mwrender/bulletdebugdraw.cpp @@ -4,7 +4,6 @@ #include -#include #include #include @@ -26,13 +25,8 @@ DebugDrawer::DebugDrawer(osg::ref_ptr parentNode, btCollisionWorld * mWorld(world), mDebugOn(true) { - mGeode = new osg::Geode; - mParentNode->addChild(mGeode); - mGeode->setNodeMask(Mask_Debug); createGeometry(); - - mParentNode->addChild(mGeode); } void DebugDrawer::createGeometry() @@ -40,6 +34,7 @@ void DebugDrawer::createGeometry() if (!mGeometry) { mGeometry = new osg::Geometry; + mGeometry->setNodeMask(Mask_Debug); mVertices = new osg::Vec3Array; @@ -50,7 +45,7 @@ void DebugDrawer::createGeometry() mGeometry->setDataVariance(osg::Object::DYNAMIC); mGeometry->addPrimitiveSet(mDrawArrays); - mGeode->addDrawable(mGeometry); + mParentNode->addChild(mGeometry); } } @@ -58,7 +53,7 @@ void DebugDrawer::destroyGeometry() { if (mGeometry) { - mGeode->removeDrawable(mGeometry); + mParentNode->removeChild(mGeometry); mGeometry = NULL; mVertices = NULL; mDrawArrays = NULL; @@ -67,7 +62,7 @@ void DebugDrawer::destroyGeometry() DebugDrawer::~DebugDrawer() { - mParentNode->removeChild(mGeode); + destroyGeometry(); } void DebugDrawer::step() diff --git a/apps/openmw/mwrender/bulletdebugdraw.hpp b/apps/openmw/mwrender/bulletdebugdraw.hpp index 1bccc20bd0..30da3aa72c 100644 --- a/apps/openmw/mwrender/bulletdebugdraw.hpp +++ b/apps/openmw/mwrender/bulletdebugdraw.hpp @@ -12,7 +12,6 @@ class btCollisionWorld; namespace osg { class Group; - class Geode; class Geometry; } @@ -24,7 +23,6 @@ class DebugDrawer : public btIDebugDraw protected: osg::ref_ptr mParentNode; btCollisionWorld *mWorld; - osg::ref_ptr mGeode; osg::ref_ptr mGeometry; osg::ref_ptr mVertices; osg::ref_ptr mDrawArrays; diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 88934414f6..4ece14f16f 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -56,6 +56,7 @@ namespace MWRender mMaxCameraDistance(192.f), mDistanceAdjusted(false), mVanityToggleQueued(false), + mVanityToggleQueuedValue(false), mViewModeToggleQueued(false), mCameraDistance(0.f) { @@ -69,6 +70,8 @@ namespace MWRender mMainCam.yaw = 0.f; mMainCam.offset = 400.f; + mCameraDistance = mMaxCameraDistance; + mUpdateCallback = new UpdateRenderCameraCallback(this); mCamera->addUpdateCallback(mUpdateCallback); } @@ -88,10 +91,10 @@ namespace MWRender const osg::Node* trackNode = mTrackingNode; if (!trackNode) return osg::Vec3d(); - osg::MatrixList mats = trackNode->getWorldMatrices(); - if (!mats.size()) + osg::NodePathList nodepaths = trackNode->getParentalNodePaths(); + if (nodepaths.empty()) return osg::Vec3d(); - const osg::Matrix& worldMat = mats[0]; + osg::Matrix worldMat = osg::computeLocalToWorld(nodepaths[0]); osg::Vec3d position = worldMat.getTrans(); if (!isFirstPerson()) @@ -108,7 +111,7 @@ namespace MWRender osg::Quat orient = osg::Quat(getPitch(), osg::Vec3d(1,0,0)) * osg::Quat(getYaw(), osg::Vec3d(0,0,1)); - osg::Vec3d offset = orient * osg::Vec3d(0, -mCameraDistance, 0); + osg::Vec3d offset = orient * osg::Vec3d(0, isFirstPerson() ? 0 : -mCameraDistance, 0); position += offset; osg::Vec3d forward = orient * osg::Vec3d(0,1,0); @@ -148,7 +151,7 @@ namespace MWRender // Now process the view changes we queued earlier if (mVanityToggleQueued) { - toggleVanityMode(!mVanity.enabled); + toggleVanityMode(mVanityToggleQueuedValue); mVanityToggleQueued = false; } if (mViewModeToggleQueued) @@ -187,12 +190,6 @@ namespace MWRender mFirstPersonView = !mFirstPersonView; processViewChange(); - - if (mFirstPersonView) { - mCameraDistance = 0.f; - } else { - mCameraDistance = mMaxCameraDistance; - } } void Camera::allowVanityMode(bool allow) @@ -206,9 +203,10 @@ namespace MWRender { // Changing the view will stop all playing animations, so if we are playing // anything important, queue the view change for later - if (isFirstPerson() && !mAnimation->upperBodyReady()) + if (mFirstPersonView && !mAnimation->upperBodyReady()) { mVanityToggleQueued = true; + mVanityToggleQueuedValue = enable; return false; } @@ -313,6 +311,8 @@ namespace MWRender float Camera::getCameraDistance() const { + if (isFirstPerson()) + return 0.f; return mCameraDistance; } @@ -395,7 +395,7 @@ namespace MWRender osg::Quat orient = osg::Quat(getPitch(), osg::Vec3d(1,0,0)) * osg::Quat(getYaw(), osg::Vec3d(0,0,1)); - osg::Vec3d offset = orient * osg::Vec3d(0, -mCameraDistance, 0); + osg::Vec3d offset = orient * osg::Vec3d(0, isFirstPerson() ? 0 : -mCameraDistance, 0); camera = focal + offset; } diff --git a/apps/openmw/mwrender/camera.hpp b/apps/openmw/mwrender/camera.hpp index fab63cd3f2..f0e53ebdd2 100644 --- a/apps/openmw/mwrender/camera.hpp +++ b/apps/openmw/mwrender/camera.hpp @@ -52,6 +52,7 @@ namespace MWRender bool mDistanceAdjusted; bool mVanityToggleQueued; + bool mVanityToggleQueuedValue; bool mViewModeToggleQueued; float mCameraDistance; diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index a6f68b5d4c..c858b57b71 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -2,6 +2,7 @@ #include +#include #include #include #include @@ -98,10 +99,16 @@ namespace MWRender osg::ref_ptr lightManager = new SceneUtil::LightManager; lightManager->setStartLight(1); - osg::ref_ptr stateset = new osg::StateSet; + osg::ref_ptr stateset = lightManager->getOrCreateStateSet(); stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON); stateset->setMode(GL_NORMALIZE, osg::StateAttribute::ON); stateset->setMode(GL_CULL_FACE, osg::StateAttribute::ON); + // assign large value to effectively turn off fog + // shaders don't respect glDisable(GL_FOG) + osg::ref_ptr fog (new osg::Fog); + fog->setStart(10000000); + fog->setEnd(10000000); + stateset->setAttributeAndModes(fog, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); osg::ref_ptr lightmodel = new osg::LightModel; lightmodel->setAmbientIntensity(osg::Vec4(0.25, 0.25, 0.25, 1.0)); @@ -123,7 +130,6 @@ namespace MWRender lightSource->setStateSetModes(*stateset, osg::StateAttribute::ON); - lightManager->setStateSet(stateset); lightManager->addChild(lightSource); mCamera->addChild(lightManager); @@ -194,7 +200,7 @@ namespace MWRender sizeX = std::max(sizeX, 0); sizeY = std::max(sizeY, 0); - mCamera->setViewport(0, 0, std::min(mSizeX, sizeX), std::min(mSizeY, sizeY)); + mCamera->setViewport(0, mSizeY-sizeY, std::min(mSizeX, sizeX), std::min(mSizeY, sizeY)); redraw(); } @@ -359,10 +365,10 @@ namespace MWRender traverse(node, nv); // Now update camera utilizing the updated head position - osg::MatrixList mats = mNodeToFollow->getWorldMatrices(); - if (!mats.size()) + osg::NodePathList nodepaths = mNodeToFollow->getParentalNodePaths(); + if (nodepaths.empty()) return; - osg::Matrix worldMat = mats[0]; + osg::Matrix worldMat = osg::computeLocalToWorld(nodepaths[0]); osg::Vec3 headOffset = worldMat.getTrans(); cam->setViewMatrixAsLookAt(headOffset + mPosOffset, headOffset + mLookAtOffset, osg::Vec3(0,0,1)); diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 7c447182f2..669d0d8a16 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "../mwbase/world.hpp" @@ -75,6 +76,7 @@ void CreatureWeaponAnimation::showCarriedLeft(bool show) void CreatureWeaponAnimation::updateParts() { + mAmmunition.reset(); mWeapon.reset(); mShield.reset(); @@ -105,9 +107,11 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot) else bonename = "Shield Bone"; - osg::ref_ptr node = mResourceSystem->getSceneManager()->createInstance(item.getClass().getModel(item)); + osg::ref_ptr node = mResourceSystem->getSceneManager()->getInstance(item.getClass().getModel(item)); osg::ref_ptr attached = SceneUtil::attach(node, mObjectRoot, bonename, bonename); mResourceSystem->getSceneManager()->notifyAttached(attached); + if (mSkeleton) + mSkeleton->markDirty(); scene.reset(new PartHolder(attached)); diff --git a/apps/openmw/mwrender/effectmanager.cpp b/apps/openmw/mwrender/effectmanager.cpp index c4e457a1fd..e2773f2dc8 100644 --- a/apps/openmw/mwrender/effectmanager.cpp +++ b/apps/openmw/mwrender/effectmanager.cpp @@ -27,7 +27,7 @@ EffectManager::~EffectManager() void EffectManager::addEffect(const std::string &model, const std::string& textureOverride, const osg::Vec3f &worldPosition, float scale) { - osg::ref_ptr node = mResourceSystem->getSceneManager()->createInstance(model); + osg::ref_ptr node = mResourceSystem->getSceneManager()->getInstance(model); node->setNodeMask(Mask_Effect); diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 9e8a545f85..4bc24c5949 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include @@ -296,9 +295,7 @@ namespace MWRender stateset->setTextureAttributeAndModes(0, texture, osg::StateAttribute::ON); stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF); stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); - osg::ref_ptr geode = new osg::Geode; - geode->addDrawable(geom); - camera->addChild(geode); + camera->addChild(geom); } mRoot->addChild(camera); @@ -402,7 +399,7 @@ namespace MWRender || bounds.mMinY > bounds.mMaxY) throw std::runtime_error("invalid map bounds"); - if (!map.mImageData.size()) + if (map.mImageData.empty()) return; Files::IMemStream istream(&map.mImageData[0], map.mImageData.size()); diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 9d5950a90e..8340ab78af 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -72,6 +72,7 @@ LocalMap::LocalMap(osgViewer::Viewer* viewer) : mViewer(viewer) , mMapResolution(Settings::Manager::getInt("local map resolution", "Map")) , mMapWorldSize(8192.f) + , mCellDistance(Settings::Manager::getInt("local map cell distance", "Map")) , mAngle(0.f) , mInterior(false) { @@ -179,7 +180,12 @@ osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, f stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON); stateset->setMode(GL_NORMALIZE, osg::StateAttribute::ON); stateset->setMode(GL_CULL_FACE, osg::StateAttribute::ON); - stateset->setMode(GL_FOG, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); + // assign large value to effectively turn off fog + // shaders don't respect glDisable(GL_FOG) + osg::ref_ptr fog (new osg::Fog); + fog->setStart(10000000); + fog->setEnd(10000000); + stateset->setAttributeAndModes(fog, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); osg::ref_ptr lightmodel = new osg::LightModel; lightmodel->setAmbientIntensity(osg::Vec4(0.3f, 0.3f, 0.3f, 1.f)); @@ -229,11 +235,11 @@ void LocalMap::setupRenderToTexture(osg::ref_ptr camera, int x, int segment.mMapTexture = texture; } -void LocalMap::requestMap(std::set cells) +void LocalMap::requestMap(std::set cells) { - for (std::set::iterator it = cells.begin(); it != cells.end(); ++it) + for (std::set::iterator it = cells.begin(); it != cells.end(); ++it) { - MWWorld::CellStore* cell = *it; + const MWWorld::CellStore* cell = *it; if (cell->isExterior()) requestExteriorMap(cell); else @@ -296,7 +302,7 @@ void LocalMap::cleanupCameras() mCamerasPendingRemoval.clear(); } -void LocalMap::requestExteriorMap(MWWorld::CellStore* cell) +void LocalMap::requestExteriorMap(const MWWorld::CellStore* cell) { mInterior = false; @@ -321,7 +327,7 @@ void LocalMap::requestExteriorMap(MWWorld::CellStore* cell) } } -void LocalMap::requestInteriorMap(MWWorld::CellStore* cell) +void LocalMap::requestInteriorMap(const MWWorld::CellStore* cell) { osg::ComputeBoundsVisitor computeBoundsVisitor; computeBoundsVisitor.setTraversalMask(Mask_Scene|Mask_Terrain); @@ -375,6 +381,7 @@ void LocalMap::requestInteriorMap(MWWorld::CellStore* cell) // If they changed by too much (for bounds, < padding is considered acceptable) then parts of the interior might not // be covered by the map anymore. // The following code detects this, and discards the CellStore's fog state if it needs to. + bool cellHasValidFog = false; if (cell->getFog()) { ESM::FogState* fog = cell->getFog(); @@ -390,13 +397,14 @@ void LocalMap::requestInteriorMap(MWWorld::CellStore* cell) || std::abs(mAngle - fog->mNorthMarkerAngle) > osg::DegreesToRadians(5.f)) { // Nuke it - cell->setFog(NULL); + cellHasValidFog = false; } else { // Looks sane, use it mBounds = osg::BoundingBox(newMin, newMax); mAngle = fog->mNorthMarkerAngle; + cellHasValidFog = true; } } @@ -434,7 +442,7 @@ void LocalMap::requestInteriorMap(MWWorld::CellStore* cell) MapSegment& segment = mSegments[std::make_pair(x,y)]; if (!segment.mFogOfWarImage) { - if (!cell->getFog()) + if (!cellHasValidFog) segment.initFogOfWar(); else { @@ -478,7 +486,7 @@ osg::Vec2f LocalMap::interiorMapToWorldPosition (float nX, float nY, int x, int return pos; } -bool LocalMap::isPositionExplored (float nX, float nY, int x, int y, bool interior) +bool LocalMap::isPositionExplored (float nX, float nY, int x, int y) { const MapSegment& segment = mSegments[std::make_pair(x, y)]; if (!segment.mFogOfWarImage) @@ -526,14 +534,14 @@ void LocalMap::updatePlayer (const osg::Vec3f& position, const osg::Quat& orient } // explore radius (squared) - const float exploreRadius = (mInterior ? 0.1f : 0.3f) * (sFogOfWarResolution-1); // explore radius from 0 to sFogOfWarResolution-1 + const float exploreRadius = 0.17f * (sFogOfWarResolution-1); // explore radius from 0 to sFogOfWarResolution-1 const float sqrExploreRadius = square(exploreRadius); const float exploreRadiusUV = exploreRadius / sFogOfWarResolution; // explore radius from 0 to 1 (UV space) // change the affected fog of war textures (in a 3x3 grid around the player) - for (int mx = -1; mx<2; ++mx) + for (int mx = -mCellDistance; mx<=mCellDistance; ++mx) { - for (int my = -1; my<2; ++my) + for (int my = -mCellDistance; my<=mCellDistance; ++my) { // is this texture affected at all? bool affected = false; @@ -624,7 +632,7 @@ void LocalMap::MapSegment::initFogOfWar() void LocalMap::MapSegment::loadFogOfWar(const ESM::FogTexture &esm) { const std::vector& data = esm.mImageData; - if (!data.size()) + if (data.empty()) { initFogOfWar(); return; diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp index 72ee0354ed..d946f4c2b6 100644 --- a/apps/openmw/mwrender/localmap.hpp +++ b/apps/openmw/mwrender/localmap.hpp @@ -52,7 +52,7 @@ namespace MWRender /** * Request a map render for the given cells. Render textures will be immediately created and can be retrieved with the getMapTexture function. */ - void requestMap (std::set cells); + void requestMap (std::set cells); /** * Remove map and fog textures for the given cell. @@ -99,7 +99,7 @@ namespace MWRender /** * Check if a given position is explored by the player (i.e. not obscured by fog of war) */ - bool isPositionExplored (float nX, float nY, int x, int y, bool interior); + bool isPositionExplored (float nX, float nY, int x, int y); osg::Group* getRoot(); @@ -143,11 +143,13 @@ namespace MWRender // size of a map segment (for exteriors, 1 cell) float mMapWorldSize; + int mCellDistance; + float mAngle; const osg::Vec2f rotatePoint(const osg::Vec2f& point, const osg::Vec2f& center, const float angle); - void requestExteriorMap(MWWorld::CellStore* cell); - void requestInteriorMap(MWWorld::CellStore* cell); + void requestExteriorMap(const MWWorld::CellStore* cell); + void requestInteriorMap(const MWWorld::CellStore* cell); osg::ref_ptr createOrthographicCamera(float left, float top, float width, float height, const osg::Vec3d& upVector, float zmin, float zmax); void setupRenderToTexture(osg::ref_ptr camera, int x, int y); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 4e367b3b18..b1f506e116 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include // TextKeyMapHolder @@ -273,6 +274,13 @@ NpcAnimation::~NpcAnimation() // all from within this destructor. ouch! && mPtr.getRefData().getCustomData() && mPtr.getClass().getInventoryStore(mPtr).getListener() == this) mPtr.getClass().getInventoryStore(mPtr).setListener(NULL, mPtr); + + // do not detach (delete) parts yet, this is done so the background thread can handle the deletion + for(size_t i = 0;i < ESM::PRT_Count;i++) + { + if (mObjectParts[i].get()) + mObjectParts[i]->unlink(); + } } NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem, @@ -352,13 +360,13 @@ public: virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) { osgUtil::CullVisitor* cv = static_cast(nv); - osg::RefMatrix* projectionMatrix = new osg::RefMatrix(*cv->getProjectionMatrix()); float fov, aspect, zNear, zFar; - if (projectionMatrix->getPerspective(fov, aspect, zNear, zFar)) + if (cv->getProjectionMatrix()->getPerspective(fov, aspect, zNear, zFar)) { fov = mFov; - projectionMatrix->makePerspective(fov, aspect, zNear, zFar); - cv->pushProjectionMatrix(projectionMatrix); + osg::RefMatrix* newProjectionMatrix = new osg::RefMatrix(*cv->getProjectionMatrix()); + newProjectionMatrix->makePerspective(fov, aspect, zNear, zFar); + cv->pushProjectionMatrix(newProjectionMatrix); traverse(node, nv); cv->popProjectionMatrix(); } @@ -415,6 +423,8 @@ int NpcAnimation::getSlot(const osg::NodePath &path) const void NpcAnimation::updateNpcBase() { clearAnimSources(); + for(size_t i = 0;i < ESM::PRT_Count;i++) + removeIndividualPart((ESM::PartReferenceType)i); const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Race *race = store.get().find(mNpc->mRace); @@ -452,50 +462,64 @@ void NpcAnimation::updateNpcBase() } bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; - std::string smodel = (mViewMode != VM_FirstPerson) ? - (!isWerewolf ? !isBeast ? "meshes\\base_anim.nif" - : "meshes\\base_animkna.nif" - : "meshes\\wolf\\skin.nif") : - (!isWerewolf ? !isBeast ? "meshes\\base_anim.1st.nif" - : "meshes\\base_animkna.1st.nif" - : "meshes\\wolf\\skin.1st.nif"); + bool isFemale = !mNpc->isMale(); + + std::string smodel; + if (mViewMode != VM_FirstPerson) + { + if (isWerewolf) + smodel = "meshes\\wolf\\skin.nif"; + else if (isBeast) + smodel = "meshes\\base_animkna.nif"; + else if (isFemale) + smodel = "meshes\\base_anim_female.nif"; + else + smodel = "meshes\\base_anim.nif"; + } + else + { + if (isWerewolf) + smodel = "meshes\\wolf\\skin.1st.nif"; + else if (isBeast) + smodel = "meshes\\base_animkna.1st.nif"; + else if (isFemale) + smodel = "meshes\\base_anim_female.1st.nif"; + else + smodel = "meshes\\base_anim.1st.nif"; + } + smodel = Misc::ResourceHelpers::correctActorModelPath(smodel, mResourceSystem->getVFS()); setObjectRoot(smodel, true, true, false); if(mViewMode != VM_FirstPerson) { + const std::string base = "meshes\\xbase_anim.nif"; + if (smodel != base) + addAnimSource(base); + addAnimSource(smodel); + if(!isWerewolf) { if(Misc::StringUtils::lowerCase(mNpc->mRace).find("argonian") != std::string::npos) addAnimSource("meshes\\xargonian_swimkna.nif"); - else if(!mNpc->isMale() && !isBeast) - addAnimSource("meshes\\xbase_anim_female.nif"); if(mNpc->mModel.length() > 0) - addAnimSource("meshes\\x"+mNpc->mModel); + addAnimSource(Misc::ResourceHelpers::correctActorModelPath("meshes\\" + mNpc->mModel, mResourceSystem->getVFS())); } } else { + const std::string base = "meshes\\xbase_anim.1st.nif"; + if (smodel != base) + addAnimSource(base); + + addAnimSource(smodel); + mObjectRoot->setNodeMask(Mask_FirstPerson); mObjectRoot->addCullCallback(new OverrideFieldOfViewCallback(mFirstPersonFieldOfView)); - if(isWerewolf) - addAnimSource(smodel); - else - { - // A bit counter-intuitive, but unlike third-person anims, it seems - // beast races get both base_anim.1st.nif and base_animkna.1st.nif. - addAnimSource("meshes\\xbase_anim.1st.nif"); - if(isBeast) - addAnimSource("meshes\\xbase_animkna.1st.nif"); - if(!mNpc->isMale() && !isBeast) - addAnimSource("meshes\\xbase_anim_female.1st.nif"); - } } - for(size_t i = 0;i < ESM::PRT_Count;i++) - removeIndividualPart((ESM::PartReferenceType)i); updateParts(); mWeaponAnimationTime->updateStartTime(); @@ -544,6 +568,7 @@ void NpcAnimation::updateParts() static const size_t slotlistsize = sizeof(slotlist)/sizeof(slotlist[0]); bool wasArrowAttached = (mAmmunition.get() != NULL); + mAmmunition.reset(); MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); for(size_t i = 0;i < slotlistsize && mViewMode != VM_HeadOnly;i++) @@ -619,116 +644,10 @@ void NpcAnimation::updateParts() showWeapons(mShowWeapons); showCarriedLeft(mShowCarriedLeft); - // Remember body parts so we only have to search through the store once for each race/gender/viewmode combination - static std::map< std::pair,std::vector > sRaceMapping; - bool isWerewolf = (mNpcType == Type_Werewolf); - int flags = (isWerewolf ? -1 : 0); - if(!mNpc->isMale()) - { - static const int Flag_Female = 1<<0; - flags |= Flag_Female; - } - if(mViewMode == VM_FirstPerson) - { - static const int Flag_FirstPerson = 1<<1; - flags |= Flag_FirstPerson; - } - std::string race = (isWerewolf ? "werewolf" : Misc::StringUtils::lowerCase(mNpc->mRace)); - std::pair thisCombination = std::make_pair(race, flags); - if (sRaceMapping.find(thisCombination) == sRaceMapping.end()) - { - typedef std::multimap BodyPartMapType; - static BodyPartMapType sBodyPartMap; - if(sBodyPartMap.empty()) - { - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Neck, ESM::PRT_Neck)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Chest, ESM::PRT_Cuirass)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Groin, ESM::PRT_Groin)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Hand, ESM::PRT_RHand)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Hand, ESM::PRT_LHand)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Wrist, ESM::PRT_RWrist)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Wrist, ESM::PRT_LWrist)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Forearm, ESM::PRT_RForearm)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Forearm, ESM::PRT_LForearm)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperarm, ESM::PRT_RUpperarm)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperarm, ESM::PRT_LUpperarm)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Foot, ESM::PRT_RFoot)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Foot, ESM::PRT_LFoot)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Ankle, ESM::PRT_RAnkle)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Ankle, ESM::PRT_LAnkle)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Knee, ESM::PRT_RKnee)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Knee, ESM::PRT_LKnee)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperleg, ESM::PRT_RLeg)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperleg, ESM::PRT_LLeg)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Tail, ESM::PRT_Tail)); - } - std::vector &parts = sRaceMapping[thisCombination]; - parts.resize(ESM::PRT_Count, NULL); - - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const MWWorld::Store &partStore = store.get(); - for(MWWorld::Store::iterator it = partStore.begin(); it != partStore.end(); ++it) - { - if(isWerewolf) - break; - const ESM::BodyPart& bodypart = *it; - if (bodypart.mData.mFlags & ESM::BodyPart::BPF_NotPlayable) - continue; - if (bodypart.mData.mType != ESM::BodyPart::MT_Skin) - continue; - - if (!Misc::StringUtils::ciEqual(bodypart.mRace, mNpc->mRace)) - continue; - - bool firstPerson = (bodypart.mId.size() >= 3) - && bodypart.mId[bodypart.mId.size()-3] == '1' - && bodypart.mId[bodypart.mId.size()-2] == 's' - && bodypart.mId[bodypart.mId.size()-1] == 't'; - if(firstPerson != (mViewMode == VM_FirstPerson)) - { - if(mViewMode == VM_FirstPerson && (bodypart.mData.mPart == ESM::BodyPart::MP_Hand || - bodypart.mData.mPart == ESM::BodyPart::MP_Wrist || - bodypart.mData.mPart == ESM::BodyPart::MP_Forearm || - bodypart.mData.mPart == ESM::BodyPart::MP_Upperarm)) - { - /* Allow 3rd person skins as a fallback for the arms if 1st person is missing. */ - BodyPartMapType::const_iterator bIt = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart)); - while(bIt != sBodyPartMap.end() && bIt->first == bodypart.mData.mPart) - { - if(!parts[bIt->second]) - parts[bIt->second] = &*it; - ++bIt; - } - } - continue; - } - - if ((!mNpc->isMale()) != (bodypart.mData.mFlags & ESM::BodyPart::BPF_Female)) - { - // Allow opposite gender's parts as fallback if parts for our gender are missing - BodyPartMapType::const_iterator bIt = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart)); - while(bIt != sBodyPartMap.end() && bIt->first == bodypart.mData.mPart) - { - if(!parts[bIt->second]) - parts[bIt->second] = &*it; - ++bIt; - } - continue; - } - - BodyPartMapType::const_iterator bIt = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart)); - while(bIt != sBodyPartMap.end() && bIt->first == bodypart.mData.mPart) - { - parts[bIt->second] = &*it; - ++bIt; - } - } - } - - const std::vector &parts = sRaceMapping[thisCombination]; + const std::vector &parts = getBodyParts(race, !mNpc->isMale(), mViewMode == VM_FirstPerson, isWerewolf); for(int part = ESM::PRT_Neck; part < ESM::PRT_Count; ++part) { if(mPartPriorities[part] < 1) @@ -746,8 +665,10 @@ void NpcAnimation::updateParts() PartHolderPtr NpcAnimation::insertBoundedPart(const std::string& model, const std::string& bonename, const std::string& bonefilter, bool enchantedGlow, osg::Vec4f* glowColor) { - osg::ref_ptr instance = mResourceSystem->getSceneManager()->createInstance(model); + osg::ref_ptr instance = mResourceSystem->getSceneManager()->getInstance(model); osg::ref_ptr attached = SceneUtil::attach(instance, mObjectRoot, bonefilter, bonename); + if (mSkeleton) + mSkeleton->markDirty(); mResourceSystem->getSceneManager()->notifyAttached(attached); if (enchantedGlow) addGlow(attached, *glowColor); @@ -946,9 +867,9 @@ void NpcAnimation::addControllers() if (mViewMode == VM_FirstPerson) { NodeMap::iterator found = mNodeMap.find("bip01 neck"); - if (found != mNodeMap.end() && dynamic_cast(found->second.get())) + if (found != mNodeMap.end()) { - osg::Node* node = found->second; + osg::MatrixTransform* node = found->second.get(); mFirstPersonNeckController = new NeckController(mObjectRoot.get()); node->addUpdateCallback(mFirstPersonNeckController); mActiveControllers.insert(std::make_pair(node, mFirstPersonNeckController)); @@ -1116,6 +1037,118 @@ void NpcAnimation::updatePtr(const MWWorld::Ptr &updated) mHeadAnimationTime->updatePtr(updated); } +// Remember body parts so we only have to search through the store once for each race/gender/viewmode combination +typedef std::map< std::pair,std::vector > RaceMapping; +static RaceMapping sRaceMapping; + +const std::vector& NpcAnimation::getBodyParts(const std::string &race, bool female, bool firstPerson, bool werewolf) +{ + static const int Flag_FirstPerson = 1<<1; + static const int Flag_Female = 1<<0; + + int flags = (werewolf ? -1 : 0); + if(female) + flags |= Flag_Female; + if(firstPerson) + flags |= Flag_FirstPerson; + + RaceMapping::iterator found = sRaceMapping.find(std::make_pair(race, flags)); + if (found != sRaceMapping.end()) + return found->second; + else + { + std::vector& parts = sRaceMapping[std::make_pair(race, flags)]; + + typedef std::multimap BodyPartMapType; + static BodyPartMapType sBodyPartMap; + if(sBodyPartMap.empty()) + { + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Neck, ESM::PRT_Neck)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Chest, ESM::PRT_Cuirass)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Groin, ESM::PRT_Groin)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Hand, ESM::PRT_RHand)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Hand, ESM::PRT_LHand)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Wrist, ESM::PRT_RWrist)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Wrist, ESM::PRT_LWrist)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Forearm, ESM::PRT_RForearm)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Forearm, ESM::PRT_LForearm)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperarm, ESM::PRT_RUpperarm)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperarm, ESM::PRT_LUpperarm)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Foot, ESM::PRT_RFoot)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Foot, ESM::PRT_LFoot)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Ankle, ESM::PRT_RAnkle)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Ankle, ESM::PRT_LAnkle)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Knee, ESM::PRT_RKnee)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Knee, ESM::PRT_LKnee)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperleg, ESM::PRT_RLeg)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperleg, ESM::PRT_LLeg)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Tail, ESM::PRT_Tail)); + } + + parts.resize(ESM::PRT_Count, NULL); + + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::Store &partStore = store.get(); + for(MWWorld::Store::iterator it = partStore.begin(); it != partStore.end(); ++it) + { + if(werewolf) + break; + const ESM::BodyPart& bodypart = *it; + if (bodypart.mData.mFlags & ESM::BodyPart::BPF_NotPlayable) + continue; + if (bodypart.mData.mType != ESM::BodyPart::MT_Skin) + continue; + + if (!Misc::StringUtils::ciEqual(bodypart.mRace, race)) + continue; + + bool partFirstPerson = (bodypart.mId.size() >= 3) + && bodypart.mId[bodypart.mId.size()-3] == '1' + && bodypart.mId[bodypart.mId.size()-2] == 's' + && bodypart.mId[bodypart.mId.size()-1] == 't'; + if(partFirstPerson != (firstPerson)) + { + if(firstPerson && (bodypart.mData.mPart == ESM::BodyPart::MP_Hand || + bodypart.mData.mPart == ESM::BodyPart::MP_Wrist || + bodypart.mData.mPart == ESM::BodyPart::MP_Forearm || + bodypart.mData.mPart == ESM::BodyPart::MP_Upperarm)) + { + /* Allow 3rd person skins as a fallback for the arms if 1st person is missing. */ + BodyPartMapType::const_iterator bIt = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart)); + while(bIt != sBodyPartMap.end() && bIt->first == bodypart.mData.mPart) + { + if(!parts[bIt->second]) + parts[bIt->second] = &*it; + ++bIt; + } + } + continue; + } + + if ((female) != (bodypart.mData.mFlags & ESM::BodyPart::BPF_Female)) + { + // Allow opposite gender's parts as fallback if parts for our gender are missing + BodyPartMapType::const_iterator bIt = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart)); + while(bIt != sBodyPartMap.end() && bIt->first == bodypart.mData.mPart) + { + if(!parts[bIt->second]) + parts[bIt->second] = &*it; + ++bIt; + } + continue; + } + + BodyPartMapType::const_iterator bIt = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart)); + while(bIt != sBodyPartMap.end() && bIt->first == bodypart.mData.mPart) + { + parts[bIt->second] = &*it; + ++bIt; + } + } + return parts; + } +} + void NpcAnimation::setAccurateAiming(bool enabled) { mAccurateAiming = enabled; diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index c5fc62f9cc..baf9c8c246 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -10,6 +10,7 @@ namespace ESM { struct NPC; + struct BodyPart; } namespace MWRender @@ -150,6 +151,10 @@ public: void setFirstPersonOffset(const osg::Vec3f& offset); virtual void updatePtr(const MWWorld::Ptr& updated); + + /// Get a list of body parts that may be used by an NPC of given race and gender. + /// @note This is a fixed size list, one list item for each ESM::PartReferenceType, may contain NULL body parts. + static const std::vector& getBodyParts(const std::string& raceId, bool female, bool firstperson, bool werewolf); }; } diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index f58ebb9174..2408d1ba0c 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -3,17 +3,10 @@ #include #include -#include #include -#include -#include -#include - -#include - -#include #include +#include #include "../mwworld/ptr.hpp" #include "../mwworld/class.hpp" @@ -23,71 +16,14 @@ #include "creatureanimation.hpp" #include "vismask.hpp" -namespace -{ - - /// Removes all particle systems and related nodes in a subgraph. - class RemoveParticlesVisitor : public osg::NodeVisitor - { - public: - RemoveParticlesVisitor() - : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - { } - - virtual void apply(osg::Node &node) - { - if (dynamic_cast(&node)) - mToRemove.push_back(&node); - - traverse(node); - } - - virtual void apply(osg::Geode& geode) - { - std::vector partsysVector; - for (unsigned int i=0; i(drw)) - partsysVector.push_back(partsys); - } - - for (std::vector::iterator it = partsysVector.begin(); it != partsysVector.end(); ++it) - geode.removeDrawable(*it); - } -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) - virtual void apply(osg::Drawable& drw) - { - if (osgParticle::ParticleSystem* partsys = dynamic_cast(&drw)) - mToRemove.push_back(partsys); - } -#endif - - void remove() - { - for (std::vector >::iterator it = mToRemove.begin(); it != mToRemove.end(); ++it) - { - // FIXME: a Drawable might have more than one parent - osg::Node* node = *it; - if (node->getNumParents()) - node->getParent(0)->removeChild(node); - } - mToRemove.clear(); - } - - private: - std::vector > mToRemove; - }; - -} - namespace MWRender { -Objects::Objects(Resource::ResourceSystem* resourceSystem, osg::ref_ptr rootNode) +Objects::Objects(Resource::ResourceSystem* resourceSystem, osg::ref_ptr rootNode, SceneUtil::UnrefQueue* unrefQueue) : mRootNode(rootNode) , mResourceSystem(resourceSystem) + , mUnrefQueue(unrefQueue) { } @@ -139,13 +75,6 @@ void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh, bool std::auto_ptr anim (new ObjectAnimation(ptr, mesh, mResourceSystem, animated, allowLight)); - if (!allowLight) - { - RemoveParticlesVisitor visitor; - anim->getObjectRoot()->accept(visitor); - visitor.remove(); - } - mObjects.insert(std::make_pair(ptr, anim.release())); } @@ -186,7 +115,11 @@ bool Objects::removeObject (const MWWorld::Ptr& ptr) delete iter->second; mObjects.erase(iter); + if (mUnrefQueue.get()) + mUnrefQueue->push(ptr.getRefData().getBaseNode()); + ptr.getRefData().getBaseNode()->getParent(0)->removeChild(ptr.getRefData().getBaseNode()); + ptr.getRefData().setBaseNode(NULL); return true; } @@ -200,6 +133,8 @@ void Objects::removeCell(const MWWorld::CellStore* store) { if(iter->first.getCell() == store) { + if (mUnrefQueue.get()) + mUnrefQueue->push(iter->second->getObjectRoot()); delete iter->second; mObjects.erase(iter++); } @@ -211,6 +146,8 @@ void Objects::removeCell(const MWWorld::CellStore* store) if(cell != mCellSceneNodes.end()) { cell->second->getParent(0)->removeChild(cell->second); + if (mUnrefQueue.get()) + mUnrefQueue->push(cell->second); mCellSceneNodes.erase(cell); } } diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index 3d0c92cb43..5b91abea4d 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -30,6 +30,11 @@ namespace MWWorld class CellStore; } +namespace SceneUtil +{ + class UnrefQueue; +} + namespace MWRender{ class Animation; @@ -65,12 +70,14 @@ class Objects{ osg::ref_ptr mRootNode; - void insertBegin(const MWWorld::Ptr& ptr); - Resource::ResourceSystem* mResourceSystem; + osg::ref_ptr mUnrefQueue; + + void insertBegin(const MWWorld::Ptr& ptr); + public: - Objects(Resource::ResourceSystem* resourceSystem, osg::ref_ptr rootNode); + Objects(Resource::ResourceSystem* resourceSystem, osg::ref_ptr rootNode, SceneUtil::UnrefQueue* unrefQueue); ~Objects(); /// @param animated Attempt to load separate keyframes from a .kf file matching the model file? diff --git a/apps/openmw/mwrender/pathgrid.cpp b/apps/openmw/mwrender/pathgrid.cpp index ae8bda1fb7..aae97fe35a 100644 --- a/apps/openmw/mwrender/pathgrid.cpp +++ b/apps/openmw/mwrender/pathgrid.cpp @@ -4,11 +4,11 @@ #include #include -#include #include #include #include +#include #include "../mwbase/world.hpp" // these includes can be removed once the static-hack is gone #include "../mwbase/environment.hpp" @@ -24,107 +24,6 @@ namespace MWRender { -static const int POINT_MESH_BASE = 35; - -osg::ref_ptr Pathgrid::createPathgridLines(const ESM::Pathgrid *pathgrid) -{ - osg::ref_ptr geom = new osg::Geometry; - - osg::ref_ptr vertices = new osg::Vec3Array; - - for(ESM::Pathgrid::EdgeList::const_iterator it = pathgrid->mEdges.begin(); - it != pathgrid->mEdges.end(); - ++it) - { - const ESM::Pathgrid::Edge &edge = *it; - const ESM::Pathgrid::Point &p1 = pathgrid->mPoints[edge.mV0], &p2 = pathgrid->mPoints[edge.mV1]; - - osg::Vec3f direction = MWMechanics::PathFinder::MakeOsgVec3(p2) - MWMechanics::PathFinder::MakeOsgVec3(p1); - osg::Vec3f lineDisplacement = (direction^osg::Vec3f(0,0,1)); - lineDisplacement.normalize(); - - lineDisplacement = lineDisplacement * POINT_MESH_BASE + - osg::Vec3f(0, 0, 10); // move lines up a little, so they will be less covered by meshes/landscape - - vertices->push_back(MWMechanics::PathFinder::MakeOsgVec3(p1) + lineDisplacement); - vertices->push_back(MWMechanics::PathFinder::MakeOsgVec3(p2) + lineDisplacement); - } - - geom->setVertexArray(vertices); - - geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINES, 0, vertices->size())); - - osg::ref_ptr colors = new osg::Vec4Array; - colors->push_back(osg::Vec4(1.f, 1.f, 0.f, 1.f)); - geom->setColorArray(colors, osg::Array::BIND_OVERALL); - - return geom; -} - -osg::ref_ptr Pathgrid::createPathgridPoints(const ESM::Pathgrid *pathgrid) -{ - osg::ref_ptr geom = new osg::Geometry; - - const float height = POINT_MESH_BASE * sqrtf(2); - - osg::ref_ptr vertices = new osg::Vec3Array; - osg::ref_ptr indices = new osg::UShortArray; - - bool first = true; - unsigned short startIndex = 0; - for(ESM::Pathgrid::PointList::const_iterator it = pathgrid->mPoints.begin(); - it != pathgrid->mPoints.end(); - ++it, startIndex += 6) - { - osg::Vec3f pointPos(MWMechanics::PathFinder::MakeOsgVec3(*it)); - - if (!first) - { - // degenerate triangle from previous octahedron - indices->push_back(startIndex - 4); // 2nd point of previous octahedron - indices->push_back(startIndex); // start point of current octahedron - } - - float pointMeshBase = static_cast(POINT_MESH_BASE); - - vertices->push_back(pointPos + osg::Vec3f(0, 0, height)); // 0 - vertices->push_back(pointPos + osg::Vec3f(-pointMeshBase, -pointMeshBase, 0)); // 1 - vertices->push_back(pointPos + osg::Vec3f(pointMeshBase, -pointMeshBase, 0)); // 2 - vertices->push_back(pointPos + osg::Vec3f(pointMeshBase, pointMeshBase, 0)); // 3 - vertices->push_back(pointPos + osg::Vec3f(-pointMeshBase, pointMeshBase, 0)); // 4 - vertices->push_back(pointPos + osg::Vec3f(0, 0, -height)); // 5 - - indices->push_back(startIndex + 0); - indices->push_back(startIndex + 1); - indices->push_back(startIndex + 2); - indices->push_back(startIndex + 5); - indices->push_back(startIndex + 3); - indices->push_back(startIndex + 4); - // degenerates - indices->push_back(startIndex + 4); - indices->push_back(startIndex + 5); - indices->push_back(startIndex + 5); - // end degenerates - indices->push_back(startIndex + 1); - indices->push_back(startIndex + 4); - indices->push_back(startIndex + 0); - indices->push_back(startIndex + 3); - indices->push_back(startIndex + 2); - - first = false; - } - - geom->setVertexArray(vertices); - - geom->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLE_STRIP, indices->size(), &(*indices)[0])); - - osg::ref_ptr colors = new osg::Vec4Array; - colors->push_back(osg::Vec4(1.f, 0.f, 0.f, 1.f)); - geom->setColorArray(colors, osg::Array::BIND_OVERALL); - - return geom; -} - Pathgrid::Pathgrid(osg::ref_ptr root) : mPathgridEnabled(false) , mRootNode(root) @@ -213,16 +112,9 @@ void Pathgrid::enableCellPathgrid(const MWWorld::CellStore *store) osg::ref_ptr cellPathGrid = new osg::PositionAttitudeTransform; cellPathGrid->setPosition(cellPathGridPos); - osg::ref_ptr lineGeode = new osg::Geode; - osg::ref_ptr lines = createPathgridLines(pathgrid); - lineGeode->addDrawable(lines); + osg::ref_ptr geometry = SceneUtil::createPathgridGeometry(*pathgrid); - osg::ref_ptr pointGeode = new osg::Geode; - osg::ref_ptr points = createPathgridPoints(pathgrid); - pointGeode->addDrawable(points); - - cellPathGrid->addChild(lineGeode); - cellPathGrid->addChild(pointGeode); + cellPathGrid->addChild(geometry); mPathGridRoot->addChild(cellPathGrid); diff --git a/apps/openmw/mwrender/pathgrid.hpp b/apps/openmw/mwrender/pathgrid.hpp index 39a6d71ed9..22bfa8e73f 100644 --- a/apps/openmw/mwrender/pathgrid.hpp +++ b/apps/openmw/mwrender/pathgrid.hpp @@ -48,9 +48,6 @@ namespace MWRender void enableCellPathgrid(const MWWorld::CellStore *store); void disableCellPathgrid(const MWWorld::CellStore *store); - // path grid meshes - osg::ref_ptr createPathgridLines(const ESM::Pathgrid *pathgrid); - osg::ref_ptr createPathgridPoints(const ESM::Pathgrid *pathgrid); public: Pathgrid(osg::ref_ptr root); ~Pathgrid(); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index ee9d93c0f2..33384a5218 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -17,7 +17,7 @@ #include #include -#include +#include #include #include @@ -26,12 +26,14 @@ #include #include #include +#include +#include #include #include +#include -#include "../mwworld/fallback.hpp" #include "../mwworld/cellstore.hpp" #include "sky.hpp" @@ -42,6 +44,7 @@ #include "camera.hpp" #include "water.hpp" #include "terrainstorage.hpp" +#include "util.hpp" namespace MWRender { @@ -125,11 +128,36 @@ namespace MWRender bool mWireframe; }; + class PreloadCommonAssetsWorkItem : public SceneUtil::WorkItem + { + public: + PreloadCommonAssetsWorkItem(Resource::ResourceSystem* resourceSystem) + : mResourceSystem(resourceSystem) + { + } + + virtual void doWork() + { + for (std::vector::const_iterator it = mModels.begin(); it != mModels.end(); ++it) + mResourceSystem->getSceneManager()->getTemplate(*it); + for (std::vector::const_iterator it = mTextures.begin(); it != mTextures.end(); ++it) + mResourceSystem->getImageManager()->getImage(*it); + } + + std::vector mModels; + std::vector mTextures; + + private: + Resource::ResourceSystem* mResourceSystem; + }; + RenderingManager::RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, Resource::ResourceSystem* resourceSystem, - const MWWorld::Fallback* fallback, const std::string& resourcePath) + const Fallback::Map* fallback, const std::string& resourcePath) : mViewer(viewer) , mRootNode(rootNode) , mResourceSystem(resourceSystem) + , mWorkQueue(new SceneUtil::WorkQueue) + , mUnrefQueue(new SceneUtil::UnrefQueue) , mFogDepth(0.f) , mUnderwaterColor(fallback->getFallbackColour("Water_UnderwaterColor")) , mUnderwaterWeight(fallback->getFallbackFloat("Water_UnderwaterColorWeight")) @@ -140,28 +168,40 @@ namespace MWRender , mFieldOfViewOverridden(false) { resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); + resourceSystem->getSceneManager()->setShaderPath(resourcePath + "/shaders"); + resourceSystem->getSceneManager()->setForceShaders(Settings::Manager::getBool("force shaders", "Shaders")); + resourceSystem->getSceneManager()->setClampLighting(Settings::Manager::getBool("clamp lighting", "Shaders")); + resourceSystem->getSceneManager()->setForcePerPixelLighting(Settings::Manager::getBool("force per pixel lighting", "Shaders")); + resourceSystem->getSceneManager()->setAutoUseNormalMaps(Settings::Manager::getBool("auto use object normal maps", "Shaders")); + resourceSystem->getSceneManager()->setNormalMapPattern(Settings::Manager::getString("normal map pattern", "Shaders")); + resourceSystem->getSceneManager()->setNormalHeightMapPattern(Settings::Manager::getString("normal height map pattern", "Shaders")); + resourceSystem->getSceneManager()->setAutoUseSpecularMaps(Settings::Manager::getBool("auto use object specular maps", "Shaders")); + resourceSystem->getSceneManager()->setSpecularMapPattern(Settings::Manager::getString("specular map pattern", "Shaders")); - osg::ref_ptr lightRoot = new SceneUtil::LightManager; - lightRoot->setLightingMask(Mask_Lighting); - mLightRoot = lightRoot; - lightRoot->setStartLight(1); + osg::ref_ptr sceneRoot = new SceneUtil::LightManager; + sceneRoot->setLightingMask(Mask_Lighting); + mSceneRoot = sceneRoot; + sceneRoot->setStartLight(1); - mRootNode->addChild(lightRoot); + mRootNode->addChild(sceneRoot); mPathgrid.reset(new Pathgrid(mRootNode)); - mObjects.reset(new Objects(mResourceSystem, lightRoot)); + mObjects.reset(new Objects(mResourceSystem, sceneRoot, mUnrefQueue.get())); mViewer->setIncrementalCompileOperation(new osgUtil::IncrementalCompileOperation); mResourceSystem->getSceneManager()->setIncrementalCompileOperation(mViewer->getIncrementalCompileOperation()); - mEffectManager.reset(new EffectManager(lightRoot, mResourceSystem)); + mEffectManager.reset(new EffectManager(sceneRoot, mResourceSystem)); - mWater.reset(new Water(mRootNode, lightRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), fallback, resourcePath)); + mWater.reset(new Water(mRootNode, sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), fallback, resourcePath)); - mTerrain.reset(new Terrain::TerrainGrid(lightRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), - new TerrainStorage(mResourceSystem->getVFS(), false), Mask_Terrain)); + mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), + new TerrainStorage(mResourceSystem->getVFS(), Settings::Manager::getString("normal map pattern", "Shaders"), Settings::Manager::getString("normal height map pattern", "Shaders"), + Settings::Manager::getBool("auto use terrain normal maps", "Shaders"), + Settings::Manager::getString("terrain specular map pattern", "Shaders"), Settings::Manager::getBool("auto use terrain specular maps", "Shaders")), + Mask_Terrain, &mResourceSystem->getSceneManager()->getShaderManager(), mUnrefQueue.get())); mCamera.reset(new Camera(mViewer->getCamera())); @@ -175,21 +215,21 @@ namespace MWRender mSunLight->setAmbient(osg::Vec4f(0,0,0,1)); mSunLight->setSpecular(osg::Vec4f(0,0,0,0)); mSunLight->setConstantAttenuation(1.f); - lightRoot->addChild(source); + sceneRoot->addChild(source); - lightRoot->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::ON); - lightRoot->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::ON); - lightRoot->getOrCreateStateSet()->setMode(GL_NORMALIZE, osg::StateAttribute::ON); + sceneRoot->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::ON); + sceneRoot->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::ON); + sceneRoot->getOrCreateStateSet()->setMode(GL_NORMALIZE, osg::StateAttribute::ON); - lightRoot->setNodeMask(Mask_Scene); - lightRoot->setName("Scene Root"); + sceneRoot->setNodeMask(Mask_Scene); + sceneRoot->setName("Scene Root"); - mSky.reset(new SkyManager(lightRoot, resourceSystem->getSceneManager())); + mSky.reset(new SkyManager(sceneRoot, resourceSystem->getSceneManager())); source->setStateSetModes(*mRootNode->getOrCreateStateSet(), osg::StateAttribute::ON); mStateUpdater = new StateUpdater; - lightRoot->addUpdateCallback(mStateUpdater); + sceneRoot->addUpdateCallback(mStateUpdater); osg::Camera::CullingMode cullingMode = osg::Camera::DEFAULT_CULLING|osg::Camera::FAR_PLANE_CULLING; @@ -230,9 +270,40 @@ namespace MWRender return mResourceSystem; } + SceneUtil::WorkQueue* RenderingManager::getWorkQueue() + { + return mWorkQueue.get(); + } + + SceneUtil::UnrefQueue* RenderingManager::getUnrefQueue() + { + return mUnrefQueue.get(); + } + + Terrain::World* RenderingManager::getTerrain() + { + return mTerrain.get(); + } + + void RenderingManager::preloadCommonAssets() + { + osg::ref_ptr workItem (new PreloadCommonAssetsWorkItem(mResourceSystem)); + mSky->listAssetsToPreload(workItem->mModels, workItem->mTextures); + mWater->listAssetsToPreload(workItem->mTextures); + + workItem->mTextures.push_back("textures/_land_default.dds"); + + mWorkQueue->addWorkItem(workItem); + } + + double RenderingManager::getReferenceTime() const + { + return mViewer->getFrameStamp()->getReferenceTime(); + } + osg::Group* RenderingManager::getLightRoot() { - return mLightRoot.get(); + return mSceneRoot.get(); } void RenderingManager::setNightEyeFactor(float factor) @@ -274,15 +345,17 @@ namespace MWRender { setAmbientColour(SceneUtil::colourFromRGB(cell->mAmbi.mAmbient)); - mSunLight->setDiffuse(SceneUtil::colourFromRGB(cell->mAmbi.mSunlight)); + osg::Vec4f diffuse = SceneUtil::colourFromRGB(cell->mAmbi.mSunlight); + mSunLight->setDiffuse(diffuse); + mSunLight->setSpecular(diffuse); mSunLight->setDirection(osg::Vec3f(1.f,-1.f,-1.f)); } - void RenderingManager::setSunColour(const osg::Vec4f &colour) + void RenderingManager::setSunColour(const osg::Vec4f& diffuse, const osg::Vec4f& specular) { // need to wrap this in a StateUpdater? - mSunLight->setDiffuse(colour); - mSunLight->setSpecular(colour); + mSunLight->setDiffuse(diffuse); + mSunLight->setSpecular(specular); } void RenderingManager::setSunDirection(const osg::Vec3f &direction) @@ -294,12 +367,6 @@ namespace MWRender mSky->setSunDirection(position); } - osg::Vec3f RenderingManager::getEyePos() - { - osg::Vec3d eye = mViewer->getCameraManipulator()->getMatrix().getTrans(); - return eye; - } - void RenderingManager::addCell(const MWWorld::CellStore *store) { mPathgrid->addCell(store); @@ -309,7 +376,6 @@ namespace MWRender if (store->getCell()->isExterior()) mTerrain->loadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); } - void RenderingManager::removeCell(const MWWorld::CellStore *store) { mPathgrid->removeCell(store); @@ -384,6 +450,8 @@ namespace MWRender void RenderingManager::update(float dt, bool paused) { + mUnrefQueue->flush(mWorkQueue.get()); + if (!paused) { mEffectManager->update(dt); @@ -504,15 +572,6 @@ namespace MWRender mutable bool mDone; }; - - class NoTraverseCallback : public osg::NodeCallback - { - public: - virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) - { - } - }; - void RenderingManager::screenshot(osg::Image *image, int w, int h) { osg::ref_ptr rttCamera (new osg::Camera); @@ -537,7 +596,7 @@ namespace MWRender image->setPixelFormat(texture->getInternalFormat()); rttCamera->setUpdateCallback(new NoTraverseCallback); - rttCamera->addChild(mLightRoot); + rttCamera->addChild(mSceneRoot); rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~Mask_GUI)); mRootNode->addChild(rttCamera); @@ -720,7 +779,7 @@ namespace MWRender { mPlayerNode = new SceneUtil::PositionAttitudeTransform; mPlayerNode->setNodeMask(Mask_Player); - mLightRoot->addChild(mPlayerNode); + mSceneRoot->addChild(mPlayerNode); } mPlayerNode->setUserDataContainer(new osg::DefaultUserDataContainer); @@ -784,13 +843,18 @@ namespace MWRender void RenderingManager::updateTextureFiltering() { - mResourceSystem->getTextureManager()->setFilterSettings( + mViewer->stopThreading(); + + mResourceSystem->getSceneManager()->setFilterSettings( Settings::Manager::getString("texture mag filter", "General"), Settings::Manager::getString("texture min filter", "General"), Settings::Manager::getString("texture mipmap", "General"), - Settings::Manager::getInt("anisotropy", "General"), - mViewer + Settings::Manager::getInt("anisotropy", "General") ); + + mTerrain->updateTextureFiltering(); + + mViewer->startThreading(); } void RenderingManager::updateAmbient() diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 58012078c4..306ae96764 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -37,9 +37,15 @@ namespace Terrain class World; } -namespace MWWorld +namespace Fallback { - class Fallback; + class Map; +} + +namespace SceneUtil +{ + class WorkQueue; + class UnrefQueue; } namespace MWRender @@ -58,13 +64,21 @@ namespace MWRender { public: RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, Resource::ResourceSystem* resourceSystem, - const MWWorld::Fallback* fallback, const std::string& resourcePath); + const Fallback::Map* fallback, const std::string& resourcePath); ~RenderingManager(); MWRender::Objects& getObjects(); Resource::ResourceSystem* getResourceSystem(); + SceneUtil::WorkQueue* getWorkQueue(); + SceneUtil::UnrefQueue* getUnrefQueue(); + Terrain::World* getTerrain(); + + void preloadCommonAssets(); + + double getReferenceTime() const; + osg::Group* getLightRoot(); void setNightEyeFactor(float factor); @@ -77,7 +91,7 @@ namespace MWRender void skySetMoonColour(bool red); void setSunDirection(const osg::Vec3f& direction); - void setSunColour(const osg::Vec4f& colour); + void setSunColour(const osg::Vec4f& diffuse, const osg::Vec4f& specular); void configureAmbient(const ESM::Cell* cell); void configureFog(const ESM::Cell* cell); @@ -123,8 +137,6 @@ namespace MWRender SkyManager* getSkyManager(); - osg::Vec3f getEyePos(); - void spawnEffect(const std::string &model, const std::string &texture, const osg::Vec3f &worldPosition, float scale = 1.f); /// Clear all savegame-specific data @@ -183,9 +195,12 @@ namespace MWRender osg::ref_ptr mViewer; osg::ref_ptr mRootNode; - osg::ref_ptr mLightRoot; + osg::ref_ptr mSceneRoot; Resource::ResourceSystem* mResourceSystem; + osg::ref_ptr mWorkQueue; + osg::ref_ptr mUnrefQueue; + osg::ref_ptr mSunLight; std::auto_ptr mPathgrid; diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp index f232ea475d..85319a632a 100644 --- a/apps/openmw/mwrender/ripplesimulation.cpp +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -3,7 +3,6 @@ #include #include -#include #include #include #include @@ -13,21 +12,21 @@ #include #include -#include +#include #include +#include +#include #include "vismask.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" -#include "../mwworld/fallback.hpp" - #include "../mwmechanics/actorutil.hpp" namespace { - void createWaterRippleStateSet(Resource::ResourceSystem* resourceSystem, const MWWorld::Fallback* fallback, osg::Node* node) + void createWaterRippleStateSet(Resource::ResourceSystem* resourceSystem, const Fallback::Map* fallback, osg::Node* node) { int rippleFrameCount = fallback->getFallbackInt("Water_RippleFrameCount"); if (rippleFrameCount <= 0) @@ -40,7 +39,11 @@ namespace { std::ostringstream texname; texname << "textures/water/" << tex << std::setw(2) << std::setfill('0') << i << ".dds"; - textures.push_back(resourceSystem->getTextureManager()->getTexture2D(texname.str(), osg::Texture::REPEAT, osg::Texture::REPEAT)); + osg::ref_ptr tex (new osg::Texture2D(resourceSystem->getImageManager()->getImage(texname.str()))); + tex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); + tex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); + resourceSystem->getSceneManager()->applyFilterSettings(tex); + textures.push_back(tex); } osg::ref_ptr controller (new NifOsg::FlipController(0, 0.3f/rippleFrameCount, textures)); @@ -78,13 +81,10 @@ namespace namespace MWRender { -RippleSimulation::RippleSimulation(osg::Group *parent, Resource::ResourceSystem* resourceSystem, const MWWorld::Fallback* fallback) +RippleSimulation::RippleSimulation(osg::Group *parent, Resource::ResourceSystem* resourceSystem, const Fallback::Map* fallback) : mParent(parent) { - osg::ref_ptr geode (new osg::Geode); - mParticleSystem = new osgParticle::ParticleSystem; - geode->addDrawable(mParticleSystem); mParticleSystem->setParticleAlignment(osgParticle::ParticleSystem::FIXED); mParticleSystem->setAlignVectorX(osg::Vec3f(1,0,0)); @@ -102,7 +102,7 @@ RippleSimulation::RippleSimulation(osg::Group *parent, Resource::ResourceSystem* mParticleNode = new osg::PositionAttitudeTransform; mParticleNode->addChild(updater); - mParticleNode->addChild(geode); + mParticleNode->addChild(mParticleSystem); mParticleNode->setNodeMask(Mask_Effect); createWaterRippleStateSet(resourceSystem, fallback, mParticleNode); diff --git a/apps/openmw/mwrender/ripplesimulation.hpp b/apps/openmw/mwrender/ripplesimulation.hpp index a4e12f2756..bca81c59b4 100644 --- a/apps/openmw/mwrender/ripplesimulation.hpp +++ b/apps/openmw/mwrender/ripplesimulation.hpp @@ -21,9 +21,9 @@ namespace Resource class ResourceSystem; } -namespace MWWorld +namespace Fallback { - class Fallback; + class Map; } namespace MWRender @@ -40,7 +40,7 @@ namespace MWRender class RippleSimulation { public: - RippleSimulation(osg::Group* parent, Resource::ResourceSystem* resourceSystem, const MWWorld::Fallback* fallback); + RippleSimulation(osg::Group* parent, Resource::ResourceSystem* resourceSystem, const Fallback::Map* fallback); ~RippleSimulation(); /// @param dt Time since the last frame diff --git a/apps/openmw/mwrender/rotatecontroller.cpp b/apps/openmw/mwrender/rotatecontroller.cpp index 11f5b943d7..534cc74906 100644 --- a/apps/openmw/mwrender/rotatecontroller.cpp +++ b/apps/openmw/mwrender/rotatecontroller.cpp @@ -44,11 +44,11 @@ void RotateController::operator()(osg::Node *node, osg::NodeVisitor *nv) osg::Quat RotateController::getWorldOrientation(osg::Node *node) { // this could be optimized later, we just need the world orientation, not the full matrix - osg::MatrixList worldMats = node->getWorldMatrices(mRelativeTo); + osg::NodePathList nodepaths = node->getParentalNodePaths(mRelativeTo); osg::Quat worldOrient; - if (!worldMats.empty()) + if (!nodepaths.empty()) { - osg::Matrixf worldMat = worldMats[0]; + osg::Matrixf worldMat = osg::computeLocalToWorld(nodepaths[0]); worldOrient = worldMat.getRotate(); } return worldOrient; diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 20e3dc07c7..b12f4510bb 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -5,18 +5,18 @@ #include #include #include -#include #include #include #include #include #include -#include #include #include -#include +#include #include #include +#include +#include #include #include @@ -30,9 +30,10 @@ #include #include -#include +#include #include +#include #include #include @@ -42,8 +43,6 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" -#include "../mwworld/fallback.hpp" - #include "vismask.hpp" #include "renderbin.hpp" @@ -134,10 +133,10 @@ private: class AtmosphereNightUpdater : public SceneUtil::StateSetUpdater { public: - AtmosphereNightUpdater(Resource::TextureManager* textureManager) + AtmosphereNightUpdater(Resource::ImageManager* imageManager) { // we just need a texture, its contents don't really matter - mTexture = textureManager->getWarningTexture(); + mTexture = new osg::Texture2D(imageManager->getWarningImage()); } void setFade(const float fade) @@ -348,14 +347,6 @@ public: { } - void apply(osg::Geode &geode) - { - for (unsigned int i=0; isize(); ++i) { float alpha = 1.f; - if (mMeshType == 0) alpha = i%2 ? 0.f : 1.f; // this is a cylinder, so every second vertex belongs to the bottom-most row + if (mMeshType == 0) alpha = (i%2) ? 0.f : 1.f; // this is a cylinder, so every second vertex belongs to the bottom-most row else if (mMeshType == 1) { if (i>= 49 && i <= 64) alpha = 0.f; // bottom-most row @@ -375,8 +366,13 @@ public: } else if (mMeshType == 2) { - osg::Vec4Array* origColors = static_cast(geom->getColorArray()); - alpha = ((*origColors)[i].x() == 1.f) ? 1.f : 0.f; + if (geom->getColorArray()) + { + osg::Vec4Array* origColors = static_cast(geom->getColorArray()); + alpha = ((*origColors)[i].x() == 1.f) ? 1.f : 0.f; + } + else + alpha = 1.f; } (*colors)[i] = osg::Vec4f(0.f, 0.f, 0.f, alpha); @@ -434,12 +430,10 @@ class CelestialBody public: CelestialBody(osg::Group* parentNode, float scaleFactor, int numUvSets) { - mGeode = new osg::Geode; - osg::ref_ptr geom = createTexturedQuad(numUvSets); - mGeode->addDrawable(geom); + mGeom = createTexturedQuad(numUvSets); mTransform = new osg::PositionAttitudeTransform; mTransform->setScale(osg::Vec3f(450,450,450) * scaleFactor); - mTransform->addChild(mGeode); + mTransform->addChild(mGeom); parentNode->addChild(mTransform); } @@ -456,7 +450,7 @@ public: protected: static const float mDistance; osg::ref_ptr mTransform; - osg::ref_ptr mGeode; + osg::ref_ptr mGeom; }; const float CelestialBody::mDistance = 1000.0f; @@ -464,36 +458,42 @@ const float CelestialBody::mDistance = 1000.0f; class Sun : public CelestialBody { public: - Sun(osg::Group* parentNode, Resource::TextureManager& textureManager) + Sun(osg::Group* parentNode, Resource::ImageManager& imageManager) : CelestialBody(parentNode, 1.0f, 1) , mUpdater(new Updater) { mTransform->addUpdateCallback(mUpdater); mTransform->setNodeMask(Mask_Sun); - osg::ref_ptr sunTex = textureManager.getTexture2D("textures/tx_sun_05.dds", - osg::Texture::CLAMP, - osg::Texture::CLAMP); + osg::ref_ptr sunTex (new osg::Texture2D(imageManager.getImage("textures/tx_sun_05.dds"))); + sunTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + sunTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - mGeode->getOrCreateStateSet()->setTextureAttributeAndModes(0, sunTex, osg::StateAttribute::ON); + mGeom->getOrCreateStateSet()->setTextureAttributeAndModes(0, sunTex, osg::StateAttribute::ON); osg::ref_ptr queryNode (new osg::Group); // Need to render after the world geometry so we can correctly test for occlusions - queryNode->getOrCreateStateSet()->setRenderBinDetails(RenderBin_OcclusionQuery, "RenderBin"); - queryNode->getOrCreateStateSet()->setNestRenderBins(false); + osg::StateSet* stateset = queryNode->getOrCreateStateSet(); + stateset->setRenderBinDetails(RenderBin_OcclusionQuery, "RenderBin"); + stateset->setNestRenderBins(false); // Set up alpha testing on the occlusion testing subgraph, that way we can get the occlusion tested fragments to match the circular shape of the sun osg::ref_ptr alphaFunc (new osg::AlphaFunc); alphaFunc->setFunction(osg::AlphaFunc::GREATER, 0.8); - queryNode->getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); - queryNode->getOrCreateStateSet()->setTextureAttributeAndModes(0, sunTex, osg::StateAttribute::ON); - queryNode->getOrCreateStateSet()->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON); + stateset->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(0, sunTex, osg::StateAttribute::ON); + stateset->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON); + // Disable writing to the color buffer. We are using this geometry for visibility tests only. + osg::ref_ptr colormask (new osg::ColorMask(0, 0, 0, 0)); + stateset->setAttributeAndModes(colormask, osg::StateAttribute::ON); + osg::ref_ptr po (new osg::PolygonOffset( -1., -1. )); + stateset->setAttributeAndModes(po, osg::StateAttribute::ON); mTransform->addChild(queryNode); mOcclusionQueryVisiblePixels = createOcclusionQueryNode(queryNode, true); mOcclusionQueryTotalPixels = createOcclusionQueryNode(queryNode, false); - createSunFlash(textureManager); + createSunFlash(imageManager); createSunGlare(); } @@ -537,6 +537,12 @@ public: } private: + class DummyComputeBoundCallback : public osg::Node::ComputeBoundingSphereCallback + { + public: + virtual osg::BoundingSphere computeBound(const osg::Node& node) const { return osg::BoundingSphere(); } + }; + /// @param queryVisible If true, queries the amount of visible pixels. If false, queries the total amount of pixels. osg::ref_ptr createOcclusionQueryNode(osg::Group* parent, bool queryVisible) { @@ -549,33 +555,21 @@ private: // Note the debug geometry setDebugDisplay(true) is always DYNAMIC and that can't be changed, not a big deal. oqn->getQueryGeometry()->setDataVariance(osg::Object::STATIC); - osg::ref_ptr queryGeode = osg::clone(mGeode.get(), osg::CopyOp::DEEP_COPY_ALL); - // Disable writing to the color buffer. We are using this geode for visibility tests only. - osg::ref_ptr colormask (new osg::ColorMask(0, 0, 0, 0)); - queryGeode->getOrCreateStateSet()->setAttributeAndModes(colormask, osg::StateAttribute::ON); + // Set up the query geometry to match the actual sun's rendering shape. osg::OcclusionQueryNode wasn't originally intended to allow this, + // normally it would automatically adjust the query geometry to match the sub graph's bounding box. The below hack is needed to + // circumvent this. + osg::Geometry* queryGeom = oqn->getQueryGeometry(); + queryGeom->setVertexArray(mGeom->getVertexArray()); + queryGeom->setTexCoordArray(0, mGeom->getTexCoordArray(0), osg::Array::BIND_PER_VERTEX); + queryGeom->removePrimitiveSet(0, oqn->getQueryGeometry()->getNumPrimitiveSets()); + queryGeom->addPrimitiveSet(mGeom->getPrimitiveSet(0)); - oqn->addChild(queryGeode); - - // Remove the default OFF|PROTECTED setting for texturing. We *want* to enable texturing for alpha testing purposes - oqn->getQueryStateSet()->removeTextureMode(0, GL_TEXTURE_2D); - - // Need to add texture coordinates so that texturing works. A bit ugly, relies on the vertex ordering - // used within OcclusionQueryNode. - osg::ref_ptr texCoordArray (new osg::Vec2Array); - for (int i=0; i<8; ++i) - { - texCoordArray->push_back(osg::Vec2(0,0)); - texCoordArray->push_back(osg::Vec2(1,0)); - texCoordArray->push_back(osg::Vec2(0,0)); - texCoordArray->push_back(osg::Vec2(1,0)); - texCoordArray->push_back(osg::Vec2(1,1)); - texCoordArray->push_back(osg::Vec2(0,1)); - texCoordArray->push_back(osg::Vec2(0,1)); - texCoordArray->push_back(osg::Vec2(1,1)); - } - - oqn->getQueryGeometry()->setTexCoordArray(0, texCoordArray, osg::Array::BIND_PER_VERTEX); + // Hack to disable unwanted awful code inside OcclusionQueryNode::computeBound. + oqn->setComputeBoundingSphereCallback(new DummyComputeBoundCallback); + // Still need a proper bounding sphere. + oqn->setInitialBound(queryGeom->getBound()); + osg::StateSet* queryStateSet = new osg::StateSet; if (queryVisible) { osg::ref_ptr depth (new osg::Depth); @@ -585,23 +579,25 @@ private: // We want the sun glare to be "infinitely" far away. depth->setZNear(1.0); depth->setZFar(1.0); - oqn->getQueryStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); + depth->setWriteMask(false); + queryStateSet->setAttributeAndModes(depth, osg::StateAttribute::ON); } else { - oqn->getQueryStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + queryStateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); } + oqn->setQueryStateSet(queryStateSet); parent->addChild(oqn); return oqn; } - void createSunFlash(Resource::TextureManager& textureManager) + void createSunFlash(Resource::ImageManager& imageManager) { - osg::ref_ptr tex = textureManager.getTexture2D("textures/tx_sun_flash_grey_05.dds", - osg::Texture::CLAMP, - osg::Texture::CLAMP); + osg::ref_ptr tex (new osg::Texture2D(imageManager.getImage("textures/tx_sun_flash_grey_05.dds"))); + tex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + tex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); osg::ref_ptr transform (new osg::PositionAttitudeTransform); const float scale = 2.6f; @@ -609,12 +605,10 @@ private: mTransform->addChild(transform); - osg::ref_ptr geode (new osg::Geode); - transform->addChild(geode); + osg::ref_ptr geom = createTexturedQuad(); + transform->addChild(geom); - geode->addDrawable(createTexturedQuad()); - - osg::StateSet* stateset = geode->getOrCreateStateSet(); + osg::StateSet* stateset = geom->getOrCreateStateSet(); stateset->setTextureAttributeAndModes(0, tex, osg::StateAttribute::ON); stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); @@ -645,11 +639,9 @@ private: camera->setRenderOrder(osg::Camera::NESTED_RENDER); camera->setAllowEventFocus(false); - osg::ref_ptr geode (new osg::Geode); osg::ref_ptr geom = osg::createTexturedQuadGeometry(osg::Vec3f(-1,-1,0), osg::Vec3f(2,0,0), osg::Vec3f(0,2,0)); - geode->addDrawable(geom); - camera->addChild(geode); + camera->addChild(geom); osg::StateSet* stateset = geom->getOrCreateStateSet(); @@ -827,7 +819,7 @@ private: , mTimeOfDayFade(1.f) , mGlareView(1.f) { - const MWWorld::Fallback* fallback = MWBase::Environment::get().getWorld()->getFallback(); + const Fallback::Map* fallback = MWBase::Environment::get().getWorld()->getFallback(); mColor = fallback->getFallbackColour("Weather_Sun_Glare_Fader_Color"); mSunGlareFaderMax = fallback->getFallbackFloat("Weather_Sun_Glare_Fader_Max"); mSunGlareFaderAngleMax = fallback->getFallbackFloat("Weather_Sun_Glare_Fader_Angle_Max"); @@ -927,21 +919,21 @@ public: Type_Secunda }; - Moon(osg::Group* parentNode, Resource::TextureManager& textureManager, float scaleFactor, Type type) + Moon(osg::Group* parentNode, Resource::ImageManager& imageManager, float scaleFactor, Type type) : CelestialBody(parentNode, scaleFactor, 2) , mType(type) , mPhase(MoonState::Phase_Unspecified) - , mUpdater(new Updater(textureManager)) + , mUpdater(new Updater(imageManager)) { setPhase(MoonState::Phase_Full); setVisible(true); - mGeode->addUpdateCallback(mUpdater); + mGeom->addUpdateCallback(mUpdater); } ~Moon() { - mGeode->removeUpdateCallback(mUpdater); + mGeom->removeUpdateCallback(mUpdater); } virtual void adjustTransparency(const float ratio) @@ -996,7 +988,7 @@ public: private: struct Updater : public SceneUtil::StateSetUpdater { - Resource::TextureManager& mTextureManager; + Resource::ImageManager& mImageManager; osg::ref_ptr mPhaseTex; osg::ref_ptr mCircleTex; float mTransparency; @@ -1004,8 +996,8 @@ private: osg::Vec4f mAtmosphereColor; osg::Vec4f mMoonColor; - Updater(Resource::TextureManager& textureManager) - : mTextureManager(textureManager) + Updater(Resource::ImageManager& imageManager) + : mImageManager(imageManager) , mPhaseTex() , mCircleTex() , mTransparency(1.0f) @@ -1050,8 +1042,12 @@ private: void setTextures(const std::string& phaseTex, const std::string& circleTex) { - mPhaseTex = mTextureManager.getTexture2D(phaseTex, osg::Texture::CLAMP, osg::Texture::CLAMP); - mCircleTex = mTextureManager.getTexture2D(circleTex, osg::Texture::CLAMP, osg::Texture::CLAMP); + mPhaseTex = new osg::Texture2D(mImageManager.getImage(phaseTex)); + mPhaseTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + mPhaseTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + mCircleTex = new osg::Texture2D(mImageManager.getImage(circleTex)); + mCircleTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + mCircleTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); reset(); } @@ -1117,6 +1113,9 @@ SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneMana , mSunEnabled(true) { osg::ref_ptr skyroot (new CameraRelativeTransform); + // Assign empty program to specify we don't want shaders + // The shaders generated by the SceneManager can't handle everything we need + skyroot->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(), osg::StateAttribute::OVERRIDE); skyroot->setNodeMask(Mask_Sky); parentNode->addChild(skyroot); @@ -1137,7 +1136,7 @@ void SkyManager::create() { assert(!mCreated); - mAtmosphereDay = mSceneManager->createInstance("meshes/sky_atmosphere.nif", mEarlyRenderBinRoot); + mAtmosphereDay = mSceneManager->getInstance("meshes/sky_atmosphere.nif", mEarlyRenderBinRoot); ModVertexAlphaVisitor modAtmosphere(0); mAtmosphereDay->accept(modAtmosphere); @@ -1150,31 +1149,31 @@ void SkyManager::create() osg::ref_ptr atmosphereNight; if (mSceneManager->getVFS()->exists("meshes/sky_night_02.nif")) - atmosphereNight = mSceneManager->createInstance("meshes/sky_night_02.nif", mAtmosphereNightNode); + atmosphereNight = mSceneManager->getInstance("meshes/sky_night_02.nif", mAtmosphereNightNode); else - atmosphereNight = mSceneManager->createInstance("meshes/sky_night_01.nif", mAtmosphereNightNode); + atmosphereNight = mSceneManager->getInstance("meshes/sky_night_01.nif", mAtmosphereNightNode); atmosphereNight->getOrCreateStateSet()->setAttributeAndModes(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); ModVertexAlphaVisitor modStars(2); atmosphereNight->accept(modStars); - mAtmosphereNightUpdater = new AtmosphereNightUpdater(mSceneManager->getTextureManager()); + mAtmosphereNightUpdater = new AtmosphereNightUpdater(mSceneManager->getImageManager()); atmosphereNight->addUpdateCallback(mAtmosphereNightUpdater); - mSun.reset(new Sun(mEarlyRenderBinRoot, *mSceneManager->getTextureManager())); + mSun.reset(new Sun(mEarlyRenderBinRoot, *mSceneManager->getImageManager())); - const MWWorld::Fallback* fallback=MWBase::Environment::get().getWorld()->getFallback(); - mMasser.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager->getTextureManager(), fallback->getFallbackFloat("Moons_Masser_Size")/125, Moon::Type_Masser)); - mSecunda.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager->getTextureManager(), fallback->getFallbackFloat("Moons_Secunda_Size")/125, Moon::Type_Secunda)); + const Fallback::Map* fallback=MWBase::Environment::get().getWorld()->getFallback(); + mMasser.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager->getImageManager(), fallback->getFallbackFloat("Moons_Masser_Size")/125, Moon::Type_Masser)); + mSecunda.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager->getImageManager(), fallback->getFallbackFloat("Moons_Secunda_Size")/125, Moon::Type_Secunda)); mCloudNode = new osg::PositionAttitudeTransform; mEarlyRenderBinRoot->addChild(mCloudNode); - mCloudMesh = mSceneManager->createInstance("meshes/sky_clouds_01.nif", mCloudNode); + mCloudMesh = mSceneManager->getInstance("meshes/sky_clouds_01.nif", mCloudNode); ModVertexAlphaVisitor modClouds(1); mCloudMesh->accept(modClouds); mCloudUpdater = new CloudUpdater; mCloudUpdater->setOpacity(1.f); mCloudMesh->addUpdateCallback(mCloudUpdater); - mCloudMesh2 = mSceneManager->createInstance("meshes/sky_clouds_01.nif", mCloudNode); + mCloudMesh2 = mSceneManager->getInstance("meshes/sky_clouds_01.nif", mCloudNode); mCloudMesh2->accept(modClouds); mCloudUpdater2 = new CloudUpdater; mCloudUpdater2->setOpacity(0.f); @@ -1273,11 +1272,7 @@ public: if (stateset->getAttribute(osg::StateAttribute::MATERIAL)) { SceneUtil::CompositeStateSetUpdater* composite = NULL; -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) osg::Callback* callback = node.getUpdateCallback(); -#else - osg::NodeCallback* callback = node.getUpdateCallback(); -#endif while (callback) { if ((composite = dynamic_cast(callback))) @@ -1337,8 +1332,12 @@ void SkyManager::createRain() mRainParticleSystem->setAlignVectorY(osg::Vec3f(0,0,-1)); osg::ref_ptr stateset (mRainParticleSystem->getOrCreateStateSet()); - stateset->setTextureAttributeAndModes(0, mSceneManager->getTextureManager()->getTexture2D("textures/tx_raindrop_01.dds", - osg::Texture::CLAMP, osg::Texture::CLAMP), osg::StateAttribute::ON); + + osg::ref_ptr raindropTex (new osg::Texture2D(mSceneManager->getImageManager()->getImage("textures/tx_raindrop_01.dds"))); + raindropTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + raindropTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + + stateset->setTextureAttributeAndModes(0, raindropTex, osg::StateAttribute::ON); stateset->setNestRenderBins(false); stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); @@ -1369,11 +1368,8 @@ void SkyManager::createRain() osg::ref_ptr updater (new osgParticle::ParticleSystemUpdater); updater->addParticleSystem(mRainParticleSystem); - osg::ref_ptr geode (new osg::Geode); - geode->addDrawable(mRainParticleSystem); - mRainNode->addChild(emitter); - mRainNode->addChild(geode); + mRainNode->addChild(mRainParticleSystem); mRainNode->addChild(updater); mRainFader = new RainFader; @@ -1524,7 +1520,7 @@ void SkyManager::setWeather(const WeatherResult& weather) mParticleNode->setNodeMask(Mask_WeatherParticles); mRootNode->addChild(mParticleNode); } - mParticleEffect = mSceneManager->createInstance(mCurrentParticleEffect, mParticleNode); + mParticleEffect = mSceneManager->getInstance(mCurrentParticleEffect, mParticleNode); SceneUtil::AssignControllerSourcesVisitor assignVisitor(boost::shared_ptr(new SceneUtil::FrameTimeSource)); mParticleEffect->accept(assignVisitor); @@ -1544,8 +1540,11 @@ void SkyManager::setWeather(const WeatherResult& weather) std::string texture = Misc::ResourceHelpers::correctTexturePath(mClouds, mSceneManager->getVFS()); - mCloudUpdater->setTexture(mSceneManager->getTextureManager()->getTexture2D(texture, - osg::Texture::REPEAT, osg::Texture::REPEAT)); + osg::ref_ptr cloudTex (new osg::Texture2D(mSceneManager->getImageManager()->getImage(texture))); + cloudTex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); + cloudTex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); + + mCloudUpdater->setTexture(cloudTex); } if (mNextClouds != weather.mNextCloudTexture) @@ -1556,8 +1555,11 @@ void SkyManager::setWeather(const WeatherResult& weather) { std::string texture = Misc::ResourceHelpers::correctTexturePath(mNextClouds, mSceneManager->getVFS()); - mCloudUpdater2->setTexture(mSceneManager->getTextureManager()->getTexture2D(texture, - osg::Texture::REPEAT, osg::Texture::REPEAT)); + osg::ref_ptr cloudTex (new osg::Texture2D(mSceneManager->getImageManager()->getImage(texture))); + cloudTex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); + cloudTex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); + + mCloudUpdater2->setTexture(cloudTex); } } @@ -1675,6 +1677,46 @@ void SkyManager::setWaterHeight(float height) mUnderwaterSwitch->setWaterLevel(height); } +void SkyManager::listAssetsToPreload(std::vector& models, std::vector& textures) +{ + models.push_back("meshes/sky_atmosphere.nif"); + if (mSceneManager->getVFS()->exists("meshes/sky_night_02.nif")) + models.push_back("meshes/sky_night_02.nif"); + models.push_back("meshes/sky_night_01.nif"); + models.push_back("meshes/sky_clouds_01.nif"); + + models.push_back("meshes\\ashcloud.nif"); + models.push_back("meshes\\blightcloud.nif"); + models.push_back("meshes\\snow.nif"); + models.push_back("meshes\\blizzard.nif"); + + textures.push_back("textures/tx_mooncircle_full_s.dds"); + textures.push_back("textures/tx_mooncircle_full_m.dds"); + + textures.push_back("textures/tx_masser_new.dds"); + textures.push_back("textures/tx_masser_one_wax.dds"); + textures.push_back("textures/tx_masser_half_wax.dds"); + textures.push_back("textures/tx_masser_three_wax.dds"); + textures.push_back("textures/tx_masser_one_wan.dds"); + textures.push_back("textures/tx_masser_half_wan.dds"); + textures.push_back("textures/tx_masser_three_wan.dds"); + textures.push_back("textures/tx_masser_full.dds"); + + textures.push_back("textures/tx_secunda_new.dds"); + textures.push_back("textures/tx_secunda_one_wax.dds"); + textures.push_back("textures/tx_secunda_half_wax.dds"); + textures.push_back("textures/tx_secunda_three_wax.dds"); + textures.push_back("textures/tx_secunda_one_wan.dds"); + textures.push_back("textures/tx_secunda_half_wan.dds"); + textures.push_back("textures/tx_secunda_three_wan.dds"); + textures.push_back("textures/tx_secunda_full.dds"); + + textures.push_back("textures/tx_sun_05.dds"); + textures.push_back("textures/tx_sun_flash_grey_05.dds"); + + textures.push_back("textures/tx_raindrop_01.dds"); +} + void SkyManager::setWaterEnabled(bool enabled) { mUnderwaterSwitch->setEnabled(enabled); diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index 0caadaa073..085eeb2be9 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -3,16 +3,17 @@ #include #include +#include #include #include -#include namespace osg { class Group; class Node; class Material; + class PositionAttitudeTransform; } namespace osgParticle @@ -153,6 +154,8 @@ namespace MWRender /// Set height of water plane (used to remove underwater weather particles) void setWaterHeight(float height); + void listAssetsToPreload(std::vector& models, std::vector& textures); + private: void create(); ///< no need to call this, automatically done on first enable() diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index ed1d8b6779..98386690b9 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -9,21 +9,9 @@ namespace MWRender { - TerrainStorage::TerrainStorage(const VFS::Manager* vfs, bool preload) - : ESMTerrain::Storage(vfs) + TerrainStorage::TerrainStorage(const VFS::Manager* vfs, const std::string& normalMapPattern, const std::string& normalHeightMapPattern, bool autoUseNormalMaps, const std::string& specularMapPattern, bool autoUseSpecularMaps) + : ESMTerrain::Storage(vfs, normalMapPattern, normalHeightMapPattern, autoUseNormalMaps, specularMapPattern, autoUseSpecularMaps) { - if (preload) - { - const MWWorld::ESMStore &esmStore = - MWBase::Environment::get().getWorld()->getStore(); - - MWWorld::Store::iterator it = esmStore.get().begin(); - for (; it != esmStore.get().end(); ++it) - { - const ESM::Land* land = &*it; - land->loadData(ESM::Land::DATA_VCLR|ESM::Land::DATA_VHGT|ESM::Land::DATA_VNML|ESM::Land::DATA_VTEX); - } - } } void TerrainStorage::getBounds(float& minX, float& maxX, float& minY, float& maxY) @@ -62,6 +50,9 @@ namespace MWRender const int flags = ESM::Land::DATA_VCLR|ESM::Land::DATA_VHGT|ESM::Land::DATA_VNML|ESM::Land::DATA_VTEX; if (!land->isDataLoaded(flags)) land->loadData(flags); + + // TODO: unload land data when it's no longer needed + return land; } diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index a12ffd540f..6fa8b98ce1 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -14,9 +14,7 @@ namespace MWRender virtual const ESM::LandTexture* getLandTexture(int index, short plugin); public: - ///@param preload Preload all Land records at startup? If using the multithreaded terrain component, this - /// should be set to "true" in order to avoid race conditions. - TerrainStorage(const VFS::Manager* vfs, bool preload); + TerrainStorage(const VFS::Manager* vfs, const std::string& normalMapPattern = "", const std::string& normalHeightMapPatteern = "", bool autoUseNormalMaps = false, const std::string& specularMapPattern = "", bool autoUseSpecularMaps = false); /// Get bounds of the whole terrain in cell units virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY); diff --git a/apps/openmw/mwrender/util.cpp b/apps/openmw/mwrender/util.cpp index e1af1c3393..876ec285fc 100644 --- a/apps/openmw/mwrender/util.cpp +++ b/apps/openmw/mwrender/util.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include namespace MWRender @@ -15,8 +15,11 @@ void overrideTexture(const std::string &texture, Resource::ResourceSystem *resou return; std::string correctedTexture = Misc::ResourceHelpers::correctTexturePath(texture, resourceSystem->getVFS()); // Not sure if wrap settings should be pulled from the overridden texture? - osg::ref_ptr tex = resourceSystem->getTextureManager()->getTexture2D(correctedTexture, osg::Texture2D::CLAMP, - osg::Texture2D::CLAMP); + osg::ref_ptr tex = new osg::Texture2D(resourceSystem->getImageManager()->getImage(correctedTexture)); + tex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + tex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + tex->setName("diffuseMap"); + osg::ref_ptr stateset; if (node->getStateSet()) stateset = static_cast(node->getStateSet()->clone(osg::CopyOp::SHALLOW_COPY)); diff --git a/apps/openmw/mwrender/util.hpp b/apps/openmw/mwrender/util.hpp index d078f0d982..815e345962 100644 --- a/apps/openmw/mwrender/util.hpp +++ b/apps/openmw/mwrender/util.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_MWRENDER_UTIL_H #define OPENMW_MWRENDER_UTIL_H +#include #include #include @@ -16,9 +17,17 @@ namespace Resource namespace MWRender { - void overrideTexture(const std::string& texture, Resource::ResourceSystem* resourceSystem, osg::ref_ptr node); + // Node callback to entirely skip the traversal. + class NoTraverseCallback : public osg::NodeCallback + { + public: + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + // no traverse() + } + }; } #endif diff --git a/apps/openmw/mwrender/vismask.hpp b/apps/openmw/mwrender/vismask.hpp index dd6e85e2ca..c70bcc71b5 100644 --- a/apps/openmw/mwrender/vismask.hpp +++ b/apps/openmw/mwrender/vismask.hpp @@ -5,6 +5,20 @@ namespace MWRender { /// Node masks used for controlling visibility of game objects. + /// @par Any node in the OSG scene graph can have a node mask. When traversing the scene graph, + /// the node visitor's traversal mask is bitwise AND'ed with the node mask. If the result of this test is + /// 0, then the node and all its child nodes are not processed. + /// @par Important traversal masks are the camera's cull mask (determines what is visible), + /// the update visitor mask (what is updated) and the intersection visitor mask (what is + /// selectable through mouse clicks or other intersection tests). + /// @par In practice, it can be useful to make a "hierarchy" out of the node masks - e.g. in OpenMW, + /// all 3D rendering nodes are child of a Scene Root node with Mask_Scene. When we do not want 3D rendering, + /// we can just omit Mask_Scene from the traversal mask, and do not need to omit all the individual + /// element masks (water, sky, terrain, etc.) since the traversal will already have stopped at the Scene root node. + /// @par The comments within the VisMask enum should give some hints as to what masks are commonly "child" of + /// another mask, or what type of node this mask is usually set on. + /// @note The mask values are not serialized within models, nor used in any other way that would break backwards + /// compatibility if the enumeration values were to be changed. Feel free to change them when it makes sense. enum VisMask { Mask_UpdateVisitor = 0x1, // reserved for separating UpdateVisitors from CullVisitors @@ -24,13 +38,11 @@ namespace MWRender Mask_Sun = (1<<10), Mask_WeatherParticles = (1<<11), - // child of Water - // top level masks Mask_Scene = (1<<12), Mask_GUI = (1<<13), - // Set on a Geode + // Set on a ParticleSystem Drawable Mask_ParticleSystem = (1<<14), // Set on cameras within the main scene graph diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index cd1f4c5113..ab828a3ad4 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -5,13 +5,10 @@ #include #include #include -#include #include #include #include -#include #include -#include #include #include #include @@ -25,7 +22,7 @@ #include #include -#include +#include #include #include @@ -34,12 +31,14 @@ #include +#include + #include "../mwworld/cellstore.hpp" -#include "../mwworld/fallback.hpp" #include "vismask.hpp" #include "ripplesimulation.hpp" #include "renderbin.hpp" +#include "util.hpp" namespace { @@ -209,18 +208,8 @@ private: osg::Plane mPlane; }; -// Node callback to entirely skip the traversal. -class NoTraverseCallback : public osg::NodeCallback -{ -public: - virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) - { - // no traverse() - } -}; - /// Moves water mesh away from the camera slightly if the camera gets too close on the Z axis. -/// The offset works around graphics artifacts that occured with the GL_DEPTH_CLAMP when the camera gets extremely close to the mesh (seen on NVIDIA at least). +/// The offset works around graphics artifacts that occurred with the GL_DEPTH_CLAMP when the camera gets extremely close to the mesh (seen on NVIDIA at least). /// Must be added as a Cull callback. class FudgeCallback : public osg::NodeCallback { @@ -313,7 +302,12 @@ public: setUpdateCallback(new NoTraverseCallback); // No need for fog here, we are already applying fog on the water surface itself as well as underwater fog - getOrCreateStateSet()->setMode(GL_FOG, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); + // assign large value to effectively turn off fog + // shaders don't respect glDisable(GL_FOG) + osg::ref_ptr fog (new osg::Fog); + fog->setStart(10000000); + fog->setEnd(10000000); + getOrCreateStateSet()->setAttributeAndModes(fog, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); mClipCullNode = new ClipCullNode; addChild(mClipCullNode); @@ -380,7 +374,9 @@ public: setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); setReferenceFrame(osg::Camera::RELATIVE_RF); - setCullMask(Mask_Effect|Mask_Scene|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Player|Mask_Lighting); + bool reflectActors = Settings::Manager::getBool("reflect actors", "Water"); + + setCullMask(Mask_Effect|Mask_Scene|Mask_Terrain|Mask_ParticleSystem|Mask_Sky|Mask_Player|Mask_Lighting|(reflectActors ? Mask_Actor : 0)); setNodeMask(Mask_RenderToTexture); unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water"); @@ -457,7 +453,7 @@ public: }; Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem *resourceSystem, osgUtil::IncrementalCompileOperation *ico, - const MWWorld::Fallback* fallback, const std::string& resourcePath) + const Fallback::Map* fallback, const std::string& resourcePath) : mParent(parent) , mSceneRoot(sceneRoot) , mResourceSystem(resourceSystem) @@ -469,25 +465,22 @@ Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem { mSimulation.reset(new RippleSimulation(parent, resourceSystem, fallback)); - osg::ref_ptr waterGeom = createWaterGeometry(CELL_SIZE*150, 40, 900); - waterGeom->setDrawCallback(new DepthClampCallback); - - mWaterGeode = new osg::Geode; - mWaterGeode->addDrawable(waterGeom); - mWaterGeode->setNodeMask(Mask_Water); + mWaterGeom = createWaterGeometry(CELL_SIZE*150, 40, 900); + mWaterGeom->setDrawCallback(new DepthClampCallback); + mWaterGeom->setNodeMask(Mask_Water); if (ico) - ico->add(mWaterGeode); + ico->add(mWaterGeom); mWaterNode = new osg::PositionAttitudeTransform; - mWaterNode->addChild(mWaterGeode); + mWaterNode->addChild(mWaterGeom); mWaterNode->addCullCallback(new FudgeCallback); // simple water fallback for the local map - osg::ref_ptr geode2 (osg::clone(mWaterGeode.get(), osg::CopyOp::DEEP_COPY_NODES)); - createSimpleWaterStateSet(geode2, mFallback->getFallbackFloat("Water_Map_Alpha")); - geode2->setNodeMask(Mask_SimpleWater); - mWaterNode->addChild(geode2); + osg::ref_ptr geom2 (osg::clone(mWaterGeom.get(), osg::CopyOp::DEEP_COPY_NODES)); + createSimpleWaterStateSet(geom2, mFallback->getFallbackFloat("Water_Map_Alpha")); + geom2->setNodeMask(Mask_SimpleWater); + mWaterNode->addChild(geom2); mSceneRoot->addChild(mWaterNode); @@ -524,10 +517,10 @@ void Water::updateWaterMaterial() mParent->addChild(mRefraction); } - createShaderWaterStateSet(mWaterGeode, mReflection, mRefraction); + createShaderWaterStateSet(mWaterGeom, mReflection, mRefraction); } else - createSimpleWaterStateSet(mWaterGeode, mFallback->getFallbackFloat("Water_World_Alpha")); + createSimpleWaterStateSet(mWaterGeom, mFallback->getFallbackFloat("Water_World_Alpha")); updateVisible(); } @@ -561,10 +554,13 @@ void Water::createSimpleWaterStateSet(osg::Node* node, float alpha) { std::ostringstream texname; texname << "textures/water/" << texture << std::setw(2) << std::setfill('0') << i << ".dds"; - textures.push_back(mResourceSystem->getTextureManager()->getTexture2D(texname.str(), osg::Texture::REPEAT, osg::Texture::REPEAT)); + osg::ref_ptr tex (new osg::Texture2D(mResourceSystem->getImageManager()->getImage(texname.str()))); + tex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); + tex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); + textures.push_back(tex); } - if (!textures.size()) + if (textures.empty()) return; float fps = mFallback->getFallbackFloat("Water_SurfaceFPS"); @@ -586,12 +582,13 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R osg::ref_ptr fragmentShader (readShader(osg::Shader::FRAGMENT, mResourcePath + "/shaders/water_fragment.glsl", defineMap)); osg::ref_ptr normalMap (new osg::Texture2D(readPngImage(mResourcePath + "/shaders/water_nm.png"))); + if (normalMap->getImage()) + normalMap->getImage()->flipVertical(); normalMap->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); normalMap->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); normalMap->setMaxAnisotropy(16); normalMap->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR); normalMap->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - normalMap->getImage()->flipVertical(); osg::ref_ptr shaderStateset = new osg::StateSet; shaderStateset->addUniform(new osg::Uniform("normalMap", 0)); @@ -650,6 +647,18 @@ Water::~Water() } } +void Water::listAssetsToPreload(std::vector &textures) +{ + int frameCount = mFallback->getFallbackInt("Water_SurfaceFrameCount"); + std::string texture = mFallback->getFallbackString("Water_SurfaceTexture"); + for (int i=0; i +#include #include #include @@ -12,7 +13,7 @@ namespace osg { class Group; class PositionAttitudeTransform; - class Geode; + class Geometry; class Node; } @@ -28,11 +29,15 @@ namespace Resource namespace MWWorld { - class Fallback; class CellStore; class Ptr; } +namespace Fallback +{ + class Map; +} + namespace MWRender { @@ -48,9 +53,9 @@ namespace MWRender osg::ref_ptr mParent; osg::ref_ptr mSceneRoot; osg::ref_ptr mWaterNode; - osg::ref_ptr mWaterGeode; + osg::ref_ptr mWaterGeom; Resource::ResourceSystem* mResourceSystem; - const MWWorld::Fallback* mFallback; + const Fallback::Map* mFallback; osg::ref_ptr mIncrementalCompileOperation; std::auto_ptr mSimulation; @@ -77,10 +82,12 @@ namespace MWRender public: Water(osg::Group* parent, osg::Group* sceneRoot, - Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, const MWWorld::Fallback* fallback, + Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, const Fallback::Map* fallback, const std::string& resourcePath); ~Water(); + void listAssetsToPreload(std::vector& textures); + void setEnabled(bool enabled); bool toggle(); diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp index 4328d5a3d7..d5fb70d164 100644 --- a/apps/openmw/mwrender/weaponanimation.cpp +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -84,7 +84,7 @@ void WeaponAnimation::attachArrow(MWWorld::Ptr actor) return; std::string model = ammo->getClass().getModel(*ammo); - osg::ref_ptr arrow = getResourceSystem()->getSceneManager()->createInstance(model, parent); + osg::ref_ptr arrow = getResourceSystem()->getSceneManager()->getInstance(model, parent); mAmmunition = PartHolderPtr(new PartHolder(arrow)); } @@ -114,10 +114,10 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength) osg::Node* weaponNode = getWeaponNode(); if (!weaponNode) return; - osg::MatrixList mats = weaponNode->getWorldMatrices(); - if (mats.empty()) + osg::NodePathList nodepaths = weaponNode->getParentalNodePaths(); + if (nodepaths.empty()) return; - osg::Vec3f launchPos = mats[0].getTrans(); + osg::Vec3f launchPos = osg::computeLocalToWorld(nodepaths[0]).getTrans(); float fThrownWeaponMinSpeed = gmst.find("fThrownWeaponMinSpeed")->getFloat(); float fThrownWeaponMaxSpeed = gmst.find("fThrownWeaponMaxSpeed")->getFloat(); @@ -140,10 +140,10 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength) return; osg::ref_ptr ammoNode = mAmmunition->getNode(); - osg::MatrixList mats = ammoNode->getWorldMatrices(); - if (mats.empty()) + osg::NodePathList nodepaths = ammoNode->getParentalNodePaths(); + if (nodepaths.empty()) return; - osg::Vec3f launchPos = mats[0].getTrans(); + osg::Vec3f launchPos = osg::computeLocalToWorld(nodepaths[0]).getTrans(); float fProjectileMinSpeed = gmst.find("fProjectileMinSpeed")->getFloat(); float fProjectileMaxSpeed = gmst.find("fProjectileMaxSpeed")->getFloat(); @@ -182,23 +182,30 @@ void WeaponAnimation::deleteControllers() void WeaponAnimation::configureControllers(float characterPitchRadians) { - if (!mSpineControllers[0]) - return; - if (mPitchFactor == 0.f || characterPitchRadians == 0.f) { - for (int i=0; i<2; ++i) - mSpineControllers[i]->setEnabled(false); + setControllerEnabled(false); return; } float pitch = characterPitchRadians * mPitchFactor; osg::Quat rotate (pitch/2, osg::Vec3f(-1,0,0)); + setControllerRotate(rotate); + setControllerEnabled(true); +} + +void WeaponAnimation::setControllerRotate(const osg::Quat& rotate) +{ for (int i=0; i<2; ++i) - { - mSpineControllers[i]->setRotate(rotate); - mSpineControllers[i]->setEnabled(true); - } + if (mSpineControllers[i]) + mSpineControllers[i]->setRotate(rotate); +} + +void WeaponAnimation::setControllerEnabled(bool enabled) +{ + for (int i=0; i<2; ++i) + if (mSpineControllers[i]) + mSpineControllers[i]->setEnabled(enabled); } } diff --git a/apps/openmw/mwrender/weaponanimation.hpp b/apps/openmw/mwrender/weaponanimation.hpp index 3bf0fb7211..d50729c622 100644 --- a/apps/openmw/mwrender/weaponanimation.hpp +++ b/apps/openmw/mwrender/weaponanimation.hpp @@ -52,6 +52,9 @@ namespace MWRender osg::ref_ptr mSpineControllers[2]; + void setControllerRotate(const osg::Quat& rotate); + void setControllerEnabled(bool enabled); + virtual osg::Group* getArrowBone() = 0; virtual osg::Node* getWeaponNode() = 0; virtual Resource::ResourceSystem* getResourceSystem() = 0; diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index db60d14d18..12e3232725 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -465,7 +465,6 @@ namespace MWScript } }; - template class OpToggleAI : public Interpreter::Opcode0 { public: @@ -520,8 +519,7 @@ namespace MWScript interpreter.installSegment5 (Compiler::Ai::opcodeStartCombatExplicit, new OpStartCombat); interpreter.installSegment5 (Compiler::Ai::opcodeStopCombat, new OpStopCombat); interpreter.installSegment5 (Compiler::Ai::opcodeStopCombatExplicit, new OpStopCombat); - interpreter.installSegment5 (Compiler::Ai::opcodeToggleAI, new OpToggleAI); - interpreter.installSegment5 (Compiler::Ai::opcodeToggleAIExplicit, new OpToggleAI); + interpreter.installSegment5 (Compiler::Ai::opcodeToggleAI, new OpToggleAI); interpreter.installSegment5 (Compiler::Ai::opcodeSetHello, new OpSetAiSetting(0)); interpreter.installSegment5 (Compiler::Ai::opcodeSetHelloExplicit, new OpSetAiSetting(0)); diff --git a/apps/openmw/mwscript/cellextensions.cpp b/apps/openmw/mwscript/cellextensions.cpp index 513c1cc42e..43e8318b2e 100644 --- a/apps/openmw/mwscript/cellextensions.cpp +++ b/apps/openmw/mwscript/cellextensions.cpp @@ -49,7 +49,7 @@ namespace MWScript world->getPlayer().setTeleported(true); if (world->findExteriorPosition(cell, pos)) { - world->changeToExteriorCell(pos); + world->changeToExteriorCell(pos, true); world->fixPosition(world->getPlayerPtr()); } else @@ -57,7 +57,7 @@ namespace MWScript // Change to interior even if findInteriorPosition() // yields false. In this case position will be zero-point. world->findInteriorPosition(cell, pos); - world->changeToInteriorCell(cell, pos); + world->changeToInteriorCell(cell, pos, true); } } }; @@ -82,7 +82,7 @@ namespace MWScript pos.rot[0] = pos.rot[1] = pos.rot[2] = 0; - world->changeToExteriorCell (pos); + world->changeToExteriorCell (pos, true); world->fixPosition(world->getPlayerPtr()); } }; diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index 4e3e010f7e..366e88f1e3 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -317,7 +317,7 @@ namespace MWScript it != invStore.end(); ++it) { if (::Misc::StringUtils::ciEqual(it->getCellRef().getSoul(), name)) - ++count; + count += it->getRefData().getCount(); } runtime.push(count); } diff --git a/apps/openmw/mwscript/dialogueextensions.cpp b/apps/openmw/mwscript/dialogueextensions.cpp index c305fb81fc..fcb7e8f3b2 100644 --- a/apps/openmw/mwscript/dialogueextensions.cpp +++ b/apps/openmw/mwscript/dialogueextensions.cpp @@ -22,12 +22,17 @@ namespace MWScript { namespace Dialogue { + template class OpJournal : public Interpreter::Opcode0 { public: virtual void execute (Interpreter::Runtime& runtime) { + MWWorld::Ptr ptr = R()(runtime, false); // required=false + if (ptr.isEmpty()) + ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); + std::string quest = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); @@ -37,7 +42,7 @@ namespace MWScript // Invoking Journal with a non-existing index is allowed, and triggers no errors. Seriously? :( try { - MWBase::Environment::get().getJournal()->addEntry (quest, index); + MWBase::Environment::get().getJournal()->addEntry (quest, index, ptr); } catch (...) { @@ -270,7 +275,7 @@ namespace MWScript void installOpcodes (Interpreter::Interpreter& interpreter) { - interpreter.installSegment5 (Compiler::Dialogue::opcodeJournal, new OpJournal); + interpreter.installSegment5 (Compiler::Dialogue::opcodeJournal, new OpJournal); interpreter.installSegment5 (Compiler::Dialogue::opcodeSetJournalIndex, new OpSetJournalIndex); interpreter.installSegment5 (Compiler::Dialogue::opcodeGetJournalIndex, new OpGetJournalIndex); interpreter.installSegment5 (Compiler::Dialogue::opcodeAddTopic, new OpAddTopic); diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 42c204ecb0..e19d7a1ad2 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -364,7 +364,7 @@ op 0x2000221: EnableLevitation op 0x2000222: GetLineOfSight op 0x2000223: GetLineOfSightExplicit op 0x2000224: ToggleAI -op 0x2000225: ToggleAIExplicit +op 0x2000225: unused op 0x2000226: COE op 0x2000227: Cast op 0x2000228: Cast, explicit @@ -447,5 +447,9 @@ op 0x20002fe: RemoveFromLevItem op 0x20002ff: SetFactionReaction op 0x2000300: EnableLevelupMenu op 0x2000301: ToggleScripts +op 0x2000302: Fixme +op 0x2000303: Fixme, explicit +op 0x2000304: Show +op 0x2000305: Show, explicit -opcodes 0x2000302-0x3ffffff unused +opcodes 0x2000304-0x3ffffff unused diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index 7a6afe2e0c..79f856398c 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -138,8 +138,7 @@ namespace MWScript InterpreterContext::InterpreterContext ( MWScript::Locals *locals, MWWorld::Ptr reference, const std::string& targetId) - : mLocals (locals), mReference (reference), - mActivationHandled (false), mTargetId (targetId) + : mLocals (locals), mReference (reference), mTargetId (targetId) { // If we run on a reference (local script, dialogue script or console with object // selected), store the ID of that reference store it so it can be inherited by @@ -477,37 +476,10 @@ namespace MWScript return static_cast(std::sqrt(diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2])); } - bool InterpreterContext::hasBeenActivated (const MWWorld::Ptr& ptr) - { - if (!mActivated.isEmpty() && mActivated==ptr) - { - mActivationHandled = true; - return true; - } - - return false; - } - - bool InterpreterContext::hasActivationBeenHandled() const - { - return mActivationHandled; - } - - void InterpreterContext::activate (const MWWorld::Ptr& ptr) - { - mActivated = ptr; - mActivationHandled = false; - } - void InterpreterContext::executeActivation(MWWorld::Ptr ptr, MWWorld::Ptr actor) { boost::shared_ptr action = (ptr.getClass().activate(ptr, actor)); action->execute (actor); - if (mActivated == ptr) - { - mActivationHandled = true; - mActivated = MWWorld::Ptr(); - } } float InterpreterContext::getSecondsPassed() const diff --git a/apps/openmw/mwscript/interpretercontext.hpp b/apps/openmw/mwscript/interpretercontext.hpp index 3c43444cc7..fdd5aa55f6 100644 --- a/apps/openmw/mwscript/interpretercontext.hpp +++ b/apps/openmw/mwscript/interpretercontext.hpp @@ -27,9 +27,6 @@ namespace MWScript Locals *mLocals; mutable MWWorld::Ptr mReference; - MWWorld::Ptr mActivated; - bool mActivationHandled; - std::string mTargetId; /// If \a id is empty, a reference the script is run from is returned or in case @@ -131,16 +128,6 @@ namespace MWScript virtual float getDistance (const std::string& name, const std::string& id = "") const; ///< @note if \a id is empty, assumes an implicit reference - bool hasBeenActivated (const MWWorld::Ptr& ptr); - ///< \attention Calling this function for the right reference will mark the action as - /// been handled. - - bool hasActivationBeenHandled() const; - - void activate (const MWWorld::Ptr& ptr); - ///< Store reference acted upon. The actual execution of the action does not - /// take place here. - void executeActivation(MWWorld::Ptr ptr, MWWorld::Ptr actor); ///< Execute the activation action for this ptr. If ptr is mActivated, mark activation as handled. diff --git a/apps/openmw/mwscript/locals.cpp b/apps/openmw/mwscript/locals.cpp index ee93ea2e7b..5c9ffa07a7 100644 --- a/apps/openmw/mwscript/locals.cpp +++ b/apps/openmw/mwscript/locals.cpp @@ -97,6 +97,32 @@ namespace MWScript return 0; } + float Locals::getFloatVar(const std::string &script, const std::string &var) + { + ensure (script); + + const Compiler::Locals& locals = MWBase::Environment::get().getScriptManager()->getLocals(script); + int index = locals.getIndex(var); + char type = locals.getType(var); + if(index != -1) + { + switch(type) + { + case 's': + return mShorts.at (index); + + case 'l': + return mLongs.at (index); + + case 'f': + return mFloats.at(index); + default: + return 0; + } + } + return 0; + } + bool Locals::setVarByInt(const std::string& script, const std::string& var, int val) { ensure (script); diff --git a/apps/openmw/mwscript/locals.hpp b/apps/openmw/mwscript/locals.hpp index bb4df42bf5..d63411a942 100644 --- a/apps/openmw/mwscript/locals.hpp +++ b/apps/openmw/mwscript/locals.hpp @@ -51,6 +51,12 @@ namespace MWScript /// \note Locals will be automatically configured first, if necessary int getIntVar (const std::string& script, const std::string& var); + /// if var does not exist, returns 0 + /// @note var needs to be in lowercase + /// + /// \note Locals will be automatically configured first, if necessary + float getFloatVar (const std::string& script, const std::string& var); + /// \note If locals have not been configured yet, no data is written. /// /// \return Locals written? diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 9c63511b27..1830be693b 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -21,6 +21,7 @@ #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" #include "../mwworld/containerstore.hpp" +#include "../mwworld/inventorystore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" @@ -141,7 +142,7 @@ namespace MWScript MWWorld::Ptr ptr = context.getReference(); - runtime.push (context.hasBeenActivated (ptr)); + runtime.push (ptr.getRefData().onActivate()); } }; @@ -157,7 +158,8 @@ namespace MWScript MWWorld::Ptr ptr = R()(runtime); - context.executeActivation(ptr, MWMechanics::getPlayer()); + if (ptr.getRefData().activateByScript()) + context.executeActivation(ptr, MWMechanics::getPlayer()); } }; @@ -509,13 +511,43 @@ namespace MWScript if (amount == 0) return; - MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr); + // Prefer dropping unequipped items first; re-stack if possible by unequipping items before dropping them. + MWWorld::InventoryStore *invStorePtr = 0; + if (ptr.getClass().hasInventoryStore(ptr)) { + invStorePtr = &ptr.getClass().getInventoryStore(ptr); + int numNotEquipped = invStorePtr->count(item); + for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) + { + MWWorld::ContainerStoreIterator it = invStorePtr->getSlot (slot); + if (it != invStorePtr->end() && ::Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), item)) + { + numNotEquipped -= it->getRefData().getCount(); + } + } + + for (int slot = 0; slot < MWWorld::InventoryStore::Slots && amount > numNotEquipped; ++slot) + { + MWWorld::ContainerStoreIterator it = invStorePtr->getSlot (slot); + if (it != invStorePtr->end() && ::Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), item)) + { + int numToRemove = it->getRefData().getCount(); + if (numToRemove > amount - numNotEquipped) + { + numToRemove = amount - numNotEquipped; + } + invStorePtr->unequipItemQuantity(*it, ptr, numToRemove); + numNotEquipped += numToRemove; + } + } + } int toRemove = amount; + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr); for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) { - if (::Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), item)) + if (::Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), item) + && (!invStorePtr || !invStorePtr->isEquipped(*iter))) { int removed = store.remove(*iter, toRemove, ptr); MWWorld::Ptr dropped = MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter, removed); @@ -817,6 +849,70 @@ namespace MWScript } }; + template + class OpShow : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime, false); + std::string var = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + + std::stringstream output; + + if (!ptr.isEmpty()) + { + const std::string& script = ptr.getClass().getScript(ptr); + if (script.empty()) + { + output << ptr.getCellRef().getRefId() << " has no script " << std::endl; + } + else + { + const Compiler::Locals& locals = + MWBase::Environment::get().getScriptManager()->getLocals(script); + char type = locals.getType(var); + switch (type) + { + case 'l': + case 's': + output << ptr.getCellRef().getRefId() << "." << var << ": " << ptr.getRefData().getLocals().getIntVar(script, var); + break; + case 'f': + output << ptr.getCellRef().getRefId() << "." << var << ": " << ptr.getRefData().getLocals().getFloatVar(script, var); + break; + default: + output << "unknown local '" << var << "' for '" << ptr.getCellRef().getRefId() << "'"; + break; + } + } + } + else + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + char type = world->getGlobalVariableType (var); + + switch (type) + { + case 's': + output << runtime.getContext().getGlobalShort (var); + break; + case 'l': + output << runtime.getContext().getGlobalLong (var); + break; + case 'f': + output << runtime.getContext().getGlobalFloat (var); + break; + default: + output << "unknown global variable"; + } + } + runtime.getContext().report(output.str()); + } + }; + template class OpShowVars : public Interpreter::Opcode0 { @@ -1014,7 +1110,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime &runtime) { - runtime.push (MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_Jail)); + runtime.push (MWBase::Environment::get().getWorld()->isPlayerInJail()); } }; @@ -1234,6 +1330,8 @@ namespace MWScript interpreter.installSegment5 (Compiler::Misc::opcodeEnableTeleporting, new OpEnableTeleporting); interpreter.installSegment5 (Compiler::Misc::opcodeShowVars, new OpShowVars); interpreter.installSegment5 (Compiler::Misc::opcodeShowVarsExplicit, new OpShowVars); + interpreter.installSegment5 (Compiler::Misc::opcodeShow, new OpShow); + interpreter.installSegment5 (Compiler::Misc::opcodeShowExplicit, new OpShow); interpreter.installSegment5 (Compiler::Misc::opcodeToggleGodMode, new OpToggleGodMode); interpreter.installSegment5 (Compiler::Misc::opcodeToggleScripts, new OpToggleScripts); interpreter.installSegment5 (Compiler::Misc::opcodeDisableLevitation, new OpEnableLevitation); diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index cdf60d3298..d5b64b7d73 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -1125,10 +1125,16 @@ namespace MWScript ptr.getClass().getCreatureStats(ptr).resurrect(); else if (ptr.getClass().getCreatureStats(ptr).isDead()) { + bool wasEnabled = ptr.getRefData().isEnabled(); MWBase::Environment::get().getWorld()->undeleteObject(ptr); - // resets runtime state such as inventory, stats and AI. does not reset position in the world MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); + + // HACK: disable/enable object to re-add it to the scene properly (need a new Animation). + MWBase::Environment::get().getWorld()->disable(ptr); + // resets runtime state such as inventory, stats and AI. does not reset position in the world ptr.getRefData().setCustomData(NULL); + if (wasEnabled) + MWBase::Environment::get().getWorld()->enable(ptr); } } }; diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 60847e745b..abde8c973b 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -318,8 +318,8 @@ namespace MWScript ptr = MWBase::Environment::get().getWorld()->moveObject(ptr,store,x,y,z); dynamic_cast(runtime.getContext()).updatePtr(base,ptr); - float ax = osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[0]); - float ay = osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[1]); + float ax = ptr.getRefData().getPosition().rot[0]; + float ay = ptr.getRefData().getPosition().rot[1]; // Note that you must specify ZRot in minutes (1 degree = 60 minutes; north = 0, east = 5400, south = 10800, west = 16200) // except for when you position the player, then degrees must be used. // See "Morrowind Scripting for Dummies (9th Edition)" pages 50 and 54 for reference. @@ -374,14 +374,14 @@ namespace MWScript } dynamic_cast(runtime.getContext()).updatePtr(base,ptr); - float ax = osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[0]); - float ay = osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[1]); + float ax = ptr.getRefData().getPosition().rot[0]; + float ay = ptr.getRefData().getPosition().rot[1]; // Note that you must specify ZRot in minutes (1 degree = 60 minutes; north = 0, east = 5400, south = 10800, west = 16200) // except for when you position the player, then degrees must be used. // See "Morrowind Scripting for Dummies (9th Edition)" pages 50 and 54 for reference. if(ptr != MWMechanics::getPlayer()) zRot = zRot/60.0f; - MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,zRot); + MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,osg::DegreesToRadians(zRot)); ptr.getClass().adjustPosition(ptr, false); } }; @@ -434,7 +434,7 @@ namespace MWScript pos.rot[2] = osg::DegreesToRadians(zRotDegrees); MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID); ref.getPtr().getCellRef().setPosition(pos); - MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,pos); + MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->placeObject(ref.getPtr(),store,pos); placed.getClass().adjustPosition(placed, true); } } @@ -482,7 +482,7 @@ namespace MWScript pos.rot[2] = osg::DegreesToRadians(zRotDegrees); MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID); ref.getPtr().getCellRef().setPosition(pos); - MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,pos); + MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->placeObject(ref.getPtr(),store,pos); placed.getClass().adjustPosition(placed, true); } }; @@ -508,6 +508,9 @@ namespace MWScript Interpreter::Type_Integer direction = runtime[0].mInteger; runtime.pop(); + if (direction < 0 || direction > 3) + throw std::runtime_error ("invalid direction"); + if (count<0) throw std::runtime_error ("count must be non-negative"); @@ -516,38 +519,10 @@ namespace MWScript for (int i=0; igetStore(), itemID, 1); - ref.getPtr().getCellRef().setPosition(ipos); - MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos); + MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(), actor, actor.getCell(), direction, distance); } } }; @@ -734,6 +709,18 @@ namespace MWScript } }; + template + class OpFixme : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + MWBase::Environment::get().getWorld()->fixPosition(ptr); + } + }; + void installOpcodes (Interpreter::Interpreter& interpreter) { interpreter.installSegment5(Compiler::Transformation::opcodeSetScale,new OpSetScale); @@ -774,6 +761,8 @@ namespace MWScript interpreter.installSegment5(Compiler::Transformation::opcodeGetStartingAngle, new OpGetStartingAngle); interpreter.installSegment5(Compiler::Transformation::opcodeGetStartingAngleExplicit, new OpGetStartingAngle); interpreter.installSegment5(Compiler::Transformation::opcodeResetActors, new OpResetActors); + interpreter.installSegment5(Compiler::Transformation::opcodeFixme, new OpFixme); + interpreter.installSegment5(Compiler::Transformation::opcodeFixmeExplicit, new OpFixme); } } } diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index 66b7e09e3a..651982344d 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -389,16 +389,6 @@ void FFmpeg_Decoder::readAll(std::vector &output) } } -void FFmpeg_Decoder::rewind() -{ - int stream_idx = mStream - mFormatCtx->streams; - if(av_seek_frame(mFormatCtx, stream_idx, 0, 0) < 0) - fail("Failed to seek in audio stream"); - av_free_packet(&mPacket); - mFrameSize = mFramePos = 0; - mNextPts = 0.0; -} - size_t FFmpeg_Decoder::getSampleOffset() { int delay = (mFrameSize-mFramePos) / av_get_channel_layout_nb_channels(mOutputChannelLayout) / diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index b27e60c9f5..cc553b161c 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -74,7 +74,6 @@ namespace MWSound virtual size_t read(char *buffer, size_t bytes); virtual void readAll(std::vector &output); - virtual void rewind(); virtual size_t getSampleOffset(); void fail(const std::string &msg); diff --git a/apps/openmw/mwsound/loudness.cpp b/apps/openmw/mwsound/loudness.cpp index 326c59c079..19e5cd5863 100644 --- a/apps/openmw/mwsound/loudness.cpp +++ b/apps/openmw/mwsound/loudness.cpp @@ -8,15 +8,16 @@ namespace MWSound { -void Sound_Loudness::analyzeLoudness(const std::vector< char >& data, int sampleRate, ChannelConfig chans, SampleType type, float valuesPerSecond) +void Sound_Loudness::analyzeLoudness(const std::vector< char >& data) { - int samplesPerSegment = static_cast(sampleRate / valuesPerSecond); - int numSamples = bytesToFrames(data.size(), chans, type); - int advance = framesToBytes(1, chans, type); + mQueue.insert( mQueue.end(), data.begin(), data.end() ); + if (!mQueue.size()) + return; + + int samplesPerSegment = static_cast(mSampleRate / mSamplesPerSec); + int numSamples = bytesToFrames(mQueue.size(), mChannelConfig, mSampleType); + int advance = framesToBytes(1, mChannelConfig, mSampleType); - mSamplesPerSec = valuesPerSecond; - mSamples.clear(); - mSamples.reserve(numSamples/samplesPerSegment); int segment=0; int sample=0; @@ -28,16 +29,16 @@ void Sound_Loudness::analyzeLoudness(const std::vector< char >& data, int sample { // get sample on a scale from -1 to 1 float value = 0; - if (type == SampleType_UInt8) - value = ((char)(data[sample*advance]^0x80))/128.f; - else if (type == SampleType_Int16) + if (mSampleType == SampleType_UInt8) + value = ((char)(mQueue[sample*advance]^0x80))/128.f; + else if (mSampleType == SampleType_Int16) { - value = *reinterpret_cast(&data[sample*advance]); + value = *reinterpret_cast(&mQueue[sample*advance]); value /= float(std::numeric_limits::max()); } - else if (type == SampleType_Float32) + else if (mSampleType == SampleType_Float32) { - value = *reinterpret_cast(&data[sample*advance]); + value = *reinterpret_cast(&mQueue[sample*advance]); value = std::max(-1.f, std::min(1.f, value)); // Float samples *should* be scaled to [-1,1] already. } @@ -53,7 +54,7 @@ void Sound_Loudness::analyzeLoudness(const std::vector< char >& data, int sample ++segment; } - mReady = true; + mQueue.erase(mQueue.begin(), mQueue.begin() + sample*advance); } diff --git a/apps/openmw/mwsound/loudness.hpp b/apps/openmw/mwsound/loudness.hpp index 366d29de51..939d83dee3 100644 --- a/apps/openmw/mwsound/loudness.hpp +++ b/apps/openmw/mwsound/loudness.hpp @@ -2,6 +2,7 @@ #define GAME_SOUND_LOUDNESS_H #include +#include #include "sound_decoder.hpp" @@ -9,29 +10,45 @@ namespace MWSound { class Sound_Loudness { - // Loudness sample info float mSamplesPerSec; + int mSampleRate; + ChannelConfig mChannelConfig; + SampleType mSampleType; + + // Loudness sample info std::vector mSamples; - volatile bool mReady; + + std::deque mQueue; public: - Sound_Loudness() : mSamplesPerSec(0.0f), mReady(false) { } + /** + * @param samplesPerSecond How many loudness values per second of audio to compute. + * @param sampleRate the sample rate of the sound buffer + * @param chans channel layout of the buffer + * @param type sample type of the buffer + */ + Sound_Loudness(float samplesPerSecond, int sampleRate, ChannelConfig chans, SampleType type) + : mSamplesPerSec(samplesPerSecond) + , mSampleRate(sampleRate) + , mChannelConfig(chans) + , mSampleType(type) + { } /** * Analyzes the energy (closely related to loudness) of a sound buffer. * The buffer will be divided into segments according to \a valuesPerSecond, * and for each segment a loudness value in the range of [0,1] will be computed. + * The computed values are then added to the mSamples vector. This method should be called continuously + * with chunks of audio until the whole audio file is processed. + * If the size of \a data does not exactly fit a number of loudness samples, the remainder + * will be kept in the mQueue and used in the next call to analyzeLoudness. * @param data the sound buffer to analyze, containing raw samples - * @param sampleRate the sample rate of the sound buffer - * @param chans channel layout of the buffer - * @param type sample type of the buffer - * @param valuesPerSecond How many loudness values per second of audio to compute. */ - void analyzeLoudness(const std::vector& data, int sampleRate, - ChannelConfig chans, SampleType type, - float valuesPerSecond); + void analyzeLoudness(const std::vector& data); - bool isReady() { return mReady; } + /** + * Get loudness at a particular time. Before calling this, the stream has to be analyzed up to that point in time (see analyzeLoudness()). + */ float getLoudnessAtTime(float sec) const; }; diff --git a/apps/openmw/mwsound/movieaudiofactory.cpp b/apps/openmw/mwsound/movieaudiofactory.cpp index 554b62d264..af9932f74e 100644 --- a/apps/openmw/mwsound/movieaudiofactory.cpp +++ b/apps/openmw/mwsound/movieaudiofactory.cpp @@ -27,7 +27,6 @@ namespace MWSound virtual void open(const std::string &fname); virtual void close(); - virtual void rewind(); virtual std::string getName(); virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type); virtual size_t read(char *buffer, size_t bytes); @@ -102,7 +101,6 @@ namespace MWSound throw std::runtime_error("unimplemented"); } void MWSoundDecoderBridge::close() {} - void MWSoundDecoderBridge::rewind() {} std::string MWSoundDecoderBridge::getName() { diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index d440206329..71459e18c3 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include @@ -212,6 +213,8 @@ private: DecoderPtr mDecoder; + std::auto_ptr mLoudnessAnalyzer; + volatile bool mIsFinished; void updateAll(bool local); @@ -222,13 +225,15 @@ private: friend class OpenAL_Output; public: - OpenAL_SoundStream(ALuint src, DecoderPtr decoder); + OpenAL_SoundStream(ALuint src, DecoderPtr decoder, bool getLoudnessData=false); ~OpenAL_SoundStream(); bool isPlaying(); double getStreamDelay() const; double getStreamOffset() const; + float getCurrentLoudness() const; + bool process(); ALint refillQueue(); }; @@ -242,9 +247,6 @@ struct OpenAL_Output::StreamThread { typedef std::vector StreamVec; StreamVec mStreams; - typedef std::vector > DecoderLoudnessVec; - DecoderLoudnessVec mDecoderLoudness; - volatile bool mQuitNow; boost::mutex mMutex; boost::condition_variable mCondVar; @@ -277,32 +279,6 @@ struct OpenAL_Output::StreamThread { ++iter; } - // Only do one loudness decode at a time, in case it takes particularly long we don't - // want to block up anything. - DecoderLoudnessVec::iterator dliter = mDecoderLoudness.begin(); - if(dliter != mDecoderLoudness.end()) - { - DecoderPtr decoder = dliter->first; - Sound_Loudness *loudness = dliter->second; - mDecoderLoudness.erase(dliter); - lock.unlock(); - - std::vector data; - ChannelConfig chans = ChannelConfig_Mono; - SampleType type = SampleType_Int16; - int srate = 48000; - try { - decoder->getInfo(&srate, &chans, &type); - decoder->readAll(data); - } - catch(std::exception &e) { - std::cerr<< "Failed to decode audio: "<analyzeLoudness(data, srate, chans, type, static_cast(sLoudnessFPS)); - lock.lock(); - continue; - } mCondVar.timed_wait(lock, boost::posix_time::milliseconds(50)); } } @@ -329,15 +305,6 @@ struct OpenAL_Output::StreamThread { { boost::lock_guard lock(mMutex); mStreams.clear(); - mDecoderLoudness.clear(); - } - - void add(DecoderPtr decoder, Sound_Loudness *loudness) - { - boost::unique_lock lock(mMutex); - mDecoderLoudness.push_back(std::make_pair(decoder, loudness)); - lock.unlock(); - mCondVar.notify_all(); } private: @@ -346,7 +313,7 @@ private: }; -OpenAL_SoundStream::OpenAL_SoundStream(ALuint src, DecoderPtr decoder) +OpenAL_SoundStream::OpenAL_SoundStream(ALuint src, DecoderPtr decoder, bool getLoudnessData) : mSource(src), mCurrentBufIdx(0), mFrameSize(0), mSilence(0), mDecoder(decoder), mIsFinished(false) { alGenBuffers(sNumBuffers, mBuffers); @@ -371,6 +338,9 @@ OpenAL_SoundStream::OpenAL_SoundStream(ALuint src, DecoderPtr decoder) mFrameSize = framesToBytes(1, chans, type); mBufferSize = static_cast(sBufferLength*srate); mBufferSize *= mFrameSize; + + if (getLoudnessData) + mLoudnessAnalyzer.reset(new Sound_Loudness(sLoudnessFPS, mSampleRate, chans, type)); } catch(std::exception&) { @@ -446,6 +416,15 @@ double OpenAL_SoundStream::getStreamOffset() const return t; } +float OpenAL_SoundStream::getCurrentLoudness() const +{ + if (!mLoudnessAnalyzer.get()) + return 0.f; + + float time = getStreamOffset(); + return mLoudnessAnalyzer->getLoudnessAtTime(time); +} + bool OpenAL_SoundStream::process() { try { @@ -493,6 +472,9 @@ ALint OpenAL_SoundStream::refillQueue() } if(got > 0) { + if (mLoudnessAnalyzer.get()) + mLoudnessAnalyzer->analyzeLoudness(data); + ALuint bufid = mBuffers[mCurrentBufIdx]; alBufferData(bufid, mFormat, &data[0], data.size(), mSampleRate); alSourceQueueBuffers(mSource, 1, &bufid); @@ -981,7 +963,7 @@ void OpenAL_Output::streamSound(DecoderPtr decoder, MWBase::SoundStreamPtr sound sound->mHandle = stream; } -void OpenAL_Output::streamSound3D(DecoderPtr decoder, MWBase::SoundStreamPtr sound) +void OpenAL_Output::streamSound3D(DecoderPtr decoder, MWBase::SoundStreamPtr sound, bool getLoudnessData) { OpenAL_SoundStream *stream = 0; ALuint source; @@ -998,7 +980,7 @@ void OpenAL_Output::streamSound3D(DecoderPtr decoder, MWBase::SoundStreamPtr sou sound->getRealVolume(), sound->getPitch(), false, sound->getUseEnv()); throwALerror(); - stream = new OpenAL_SoundStream(source, decoder); + stream = new OpenAL_SoundStream(source, decoder, getLoudnessData); mStreamThread->add(stream); mActiveStreams.push_back(sound); } @@ -1045,6 +1027,14 @@ double OpenAL_Output::getStreamOffset(MWBase::SoundStreamPtr sound) return stream->getStreamOffset(); } +float OpenAL_Output::getStreamLoudness(MWBase::SoundStreamPtr sound) +{ + if(!sound->mHandle) return 0.0; + OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); + boost::lock_guard lock(mStreamThread->mMutex); + return stream->getCurrentLoudness(); +} + bool OpenAL_Output::isStreamPlaying(MWBase::SoundStreamPtr sound) { if(!sound->mHandle) return false; @@ -1144,12 +1134,6 @@ void OpenAL_Output::resumeSounds(int types) } -void OpenAL_Output::loadLoudnessAsync(DecoderPtr decoder, Sound_Loudness *loudness) -{ - mStreamThread->add(decoder, loudness); -} - - OpenAL_Output::OpenAL_Output(SoundManager &mgr) : Sound_Output(mgr), mDevice(0), mContext(0) , mListenerPos(0.0f, 0.0f, 0.0f), mListenerEnv(Env_Normal) diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index 4986cd3a03..24c8609d69 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -63,10 +63,11 @@ namespace MWSound virtual void updateSound(MWBase::SoundPtr sound); virtual void streamSound(DecoderPtr decoder, MWBase::SoundStreamPtr sound); - virtual void streamSound3D(DecoderPtr decoder, MWBase::SoundStreamPtr sound); + virtual void streamSound3D(DecoderPtr decoder, MWBase::SoundStreamPtr sound, bool getLoudnessData); virtual void finishStream(MWBase::SoundStreamPtr sound); virtual double getStreamDelay(MWBase::SoundStreamPtr sound); virtual double getStreamOffset(MWBase::SoundStreamPtr sound); + virtual float getStreamLoudness(MWBase::SoundStreamPtr sound); virtual bool isStreamPlaying(MWBase::SoundStreamPtr sound); virtual void updateStream(MWBase::SoundStreamPtr sound); @@ -78,8 +79,6 @@ namespace MWSound virtual void pauseSounds(int types); virtual void resumeSounds(int types); - virtual void loadLoudnessAsync(DecoderPtr decoder, Sound_Loudness *loudness); - OpenAL_Output(SoundManager &mgr); virtual ~OpenAL_Output(); }; diff --git a/apps/openmw/mwsound/sound_buffer.hpp b/apps/openmw/mwsound/sound_buffer.hpp index 8f8e43da4b..5ca3a45da7 100644 --- a/apps/openmw/mwsound/sound_buffer.hpp +++ b/apps/openmw/mwsound/sound_buffer.hpp @@ -3,11 +3,7 @@ #include -#include "soundmanagerimp.hpp" #include "sound_output.hpp" -#include "loudness.hpp" - -#include "../mwworld/ptr.hpp" namespace MWSound { diff --git a/apps/openmw/mwsound/sound_decoder.hpp b/apps/openmw/mwsound/sound_decoder.hpp index 1be9dd3749..34bae87d74 100644 --- a/apps/openmw/mwsound/sound_decoder.hpp +++ b/apps/openmw/mwsound/sound_decoder.hpp @@ -42,7 +42,6 @@ namespace MWSound virtual size_t read(char *buffer, size_t bytes) = 0; virtual void readAll(std::vector &output); - virtual void rewind() = 0; virtual size_t getSampleOffset() = 0; Sound_Decoder(const VFS::Manager* resourceMgr) : mResourceMgr(resourceMgr) diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index 98eba8466c..e34d888ee8 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -12,7 +12,6 @@ namespace MWSound class SoundManager; struct Sound_Decoder; class Sound; - class Sound_Loudness; // An opaque handle for the implementation's sound buffers. typedef void *Sound_Handle; @@ -42,10 +41,11 @@ namespace MWSound virtual void updateSound(MWBase::SoundPtr sound) = 0; virtual void streamSound(DecoderPtr decoder, MWBase::SoundStreamPtr sound) = 0; - virtual void streamSound3D(DecoderPtr decoder, MWBase::SoundStreamPtr sound) = 0; + virtual void streamSound3D(DecoderPtr decoder, MWBase::SoundStreamPtr sound, bool getLoudnessData) = 0; virtual void finishStream(MWBase::SoundStreamPtr sound) = 0; virtual double getStreamDelay(MWBase::SoundStreamPtr sound) = 0; virtual double getStreamOffset(MWBase::SoundStreamPtr sound) = 0; + virtual float getStreamLoudness(MWBase::SoundStreamPtr sound) = 0; virtual bool isStreamPlaying(MWBase::SoundStreamPtr sound) = 0; virtual void updateStream(MWBase::SoundStreamPtr sound) = 0; @@ -57,11 +57,6 @@ namespace MWSound virtual void pauseSounds(int types) = 0; virtual void resumeSounds(int types) = 0; - // HACK: The sound output implementation really shouldn't be handling - // asynchronous loudness data loading, but it's currently where we have - // a background processing thread. - virtual void loadLoudnessAsync(DecoderPtr decoder, Sound_Loudness *loudness) = 0; - Sound_Output& operator=(const Sound_Output &rhs); Sound_Output(const Sound_Output &rhs); diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index e8ceaa40f5..663f888524 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -219,7 +219,7 @@ namespace MWSound return sfx; } - DecoderPtr SoundManager::loadVoice(const std::string &voicefile, Sound_Loudness **lipdata) + DecoderPtr SoundManager::loadVoice(const std::string &voicefile) { DecoderPtr decoder = getDecoder(); // Workaround: Bethesda at some point converted some of the files to mp3, but the references were kept as .wav. @@ -234,21 +234,6 @@ namespace MWSound decoder->open(file); } - NameLoudnessRefMap::iterator lipiter = mVoiceLipNameMap.find(voicefile); - if(lipiter != mVoiceLipNameMap.end()) - { - *lipdata = lipiter->second; - return decoder; - } - - mVoiceLipBuffers.insert(mVoiceLipBuffers.end(), Sound_Loudness()); - lipiter = mVoiceLipNameMap.insert( - std::make_pair(voicefile, &mVoiceLipBuffers.back()) - ).first; - - mOutput->loadLoudnessAsync(decoder, lipiter->second); - - *lipdata = lipiter->second; return decoder; } @@ -273,7 +258,7 @@ namespace MWSound { sound.reset(new Stream(pos, 1.0f, basevol, 1.0f, minDistance, maxDistance, Play_Normal|Play_TypeVoice|Play_3D)); - mOutput->streamSound3D(decoder, sound); + mOutput->streamSound3D(decoder, sound, true); } return sound; } @@ -366,7 +351,7 @@ namespace MWSound else filelist = mMusicFiles[mCurrentPlaylist]; - if(!filelist.size()) + if(filelist.empty()) return; int i = Misc::Rng::rollDice(filelist.size()); @@ -400,28 +385,22 @@ namespace MWSound { std::string voicefile = "Sound/"+filename; - Sound_Loudness *loudness; mVFS->normalizeFilename(voicefile); - DecoderPtr decoder = loadVoice(voicefile, &loudness); + DecoderPtr decoder = loadVoice(voicefile); - if(!loudness->isReady()) - mPendingSaySounds[ptr] = std::make_pair(decoder, loudness); - else + MWBase::World *world = MWBase::Environment::get().getWorld(); + const osg::Vec3f pos = world->getActorHeadTransform(ptr).getTrans(); + + SaySoundMap::iterator oldIt = mActiveSaySounds.find(ptr); + if (oldIt != mActiveSaySounds.end()) { - MWBase::World *world = MWBase::Environment::get().getWorld(); - const osg::Vec3f pos = world->getActorHeadTransform(ptr).getTrans(); - - SaySoundMap::iterator oldIt = mActiveSaySounds.find(ptr); - if (oldIt != mActiveSaySounds.end()) - { - mOutput->finishStream(oldIt->second.first); - mActiveSaySounds.erase(oldIt); - } - - MWBase::SoundStreamPtr sound = playVoice(decoder, pos, (ptr == MWMechanics::getPlayer())); - - mActiveSaySounds.insert(std::make_pair(ptr, std::make_pair(sound, loudness))); + mOutput->finishStream(oldIt->second); + mActiveSaySounds.erase(oldIt); } + + MWBase::SoundStreamPtr sound = playVoice(decoder, pos, (ptr == MWMechanics::getPlayer())); + + mActiveSaySounds.insert(std::make_pair(ptr, sound)); } catch(std::exception &e) { @@ -434,10 +413,8 @@ namespace MWSound SaySoundMap::const_iterator snditer = mActiveSaySounds.find(ptr); if(snditer != mActiveSaySounds.end()) { - MWBase::SoundStreamPtr sound = snditer->second.first; - Sound_Loudness *loudness = snditer->second.second; - float sec = mOutput->getStreamOffset(sound); - return loudness->getLoudnessAtTime(sec); + MWBase::SoundStreamPtr sound = snditer->second; + return mOutput->getStreamLoudness(sound); } return 0.0f; @@ -451,24 +428,18 @@ namespace MWSound { std::string voicefile = "Sound/"+filename; - Sound_Loudness *loudness; mVFS->normalizeFilename(voicefile); - DecoderPtr decoder = loadVoice(voicefile, &loudness); + DecoderPtr decoder = loadVoice(voicefile); - if(!loudness->isReady()) - mPendingSaySounds[MWWorld::ConstPtr()] = std::make_pair(decoder, loudness); - else + SaySoundMap::iterator oldIt = mActiveSaySounds.find(MWWorld::ConstPtr()); + if (oldIt != mActiveSaySounds.end()) { - SaySoundMap::iterator oldIt = mActiveSaySounds.find(MWWorld::ConstPtr()); - if (oldIt != mActiveSaySounds.end()) - { - mOutput->finishStream(oldIt->second.first); - mActiveSaySounds.erase(oldIt); - } - - mActiveSaySounds.insert(std::make_pair(MWWorld::ConstPtr(), - std::make_pair(playVoice(decoder, osg::Vec3f(), true), loudness))); + mOutput->finishStream(oldIt->second); + mActiveSaySounds.erase(oldIt); } + + mActiveSaySounds.insert(std::make_pair(MWWorld::ConstPtr(), + playVoice(decoder, osg::Vec3f(), true))); } catch(std::exception &e) { @@ -481,11 +452,11 @@ namespace MWSound SaySoundMap::const_iterator snditer = mActiveSaySounds.find(ptr); if(snditer != mActiveSaySounds.end()) { - if(mOutput->isStreamPlaying(snditer->second.first)) + if(mOutput->isStreamPlaying(snditer->second)) return false; return true; } - return mPendingSaySounds.find(ptr) == mPendingSaySounds.end(); + return true; } void SoundManager::stopSay(const MWWorld::ConstPtr &ptr) @@ -493,10 +464,9 @@ namespace MWSound SaySoundMap::iterator snditer = mActiveSaySounds.find(ptr); if(snditer != mActiveSaySounds.end()) { - mOutput->finishStream(snditer->second.first); + mOutput->finishStream(snditer->second); mActiveSaySounds.erase(snditer); } - mPendingSaySounds.erase(ptr); } @@ -691,7 +661,7 @@ namespace MWSound sayiter->first != MWMechanics::getPlayer() && sayiter->first.getCell() == cell) { - mOutput->finishStream(sayiter->second.first); + mOutput->finishStream(sayiter->second); } ++sayiter; } @@ -837,7 +807,7 @@ namespace MWSound timePassed = 0.0f; // Make sure music is still playing - if(!isMusicPlaying()) + if(!isMusicPlaying() && !mCurrentPlaylist.empty()) startRandomTitle(); Environment env = Env_Normal; @@ -901,51 +871,11 @@ namespace MWSound ++snditer; } - SayDecoderMap::iterator penditer = mPendingSaySounds.begin(); - while(penditer != mPendingSaySounds.end()) - { - Sound_Loudness *loudness = penditer->second.second; - if(loudness->isReady()) - { - try { - DecoderPtr decoder = penditer->second.first; - decoder->rewind(); - - MWBase::SoundStreamPtr sound; - MWWorld::ConstPtr ptr = penditer->first; - - SaySoundMap::iterator old = mActiveSaySounds.find(ptr); - if (old != mActiveSaySounds.end()) - { - mOutput->finishStream(old->second.first); - mActiveSaySounds.erase(old); - } - - if(ptr == MWWorld::ConstPtr()) - sound = playVoice(decoder, osg::Vec3f(), true); - else - { - MWBase::World *world = MWBase::Environment::get().getWorld(); - const osg::Vec3f pos = world->getActorHeadTransform(ptr).getTrans(); - sound = playVoice(decoder, pos, (ptr == MWMechanics::getPlayer())); - } - mActiveSaySounds.insert(std::make_pair(ptr, std::make_pair(sound, loudness))); - } - catch(std::exception &e) { - std::cerr<< "Sound Error: "<first; - MWBase::SoundStreamPtr sound = sayiter->second.first; + MWBase::SoundStreamPtr sound = sayiter->second; if(!ptr.isEmpty() && sound->getIs3D()) { MWBase::World *world = MWBase::Environment::get().getWorld(); @@ -1040,7 +970,7 @@ namespace MWSound SaySoundMap::iterator sayiter = mActiveSaySounds.begin(); for(;sayiter != mActiveSaySounds.end();++sayiter) { - MWBase::SoundStreamPtr sound = sayiter->second.first; + MWBase::SoundStreamPtr sound = sayiter->second; sound->setBaseVolume(volumeFromType(sound->getPlayType())); mOutput->updateStream(sound); } @@ -1080,16 +1010,9 @@ namespace MWSound SaySoundMap::iterator sayiter = mActiveSaySounds.find(old); if(sayiter != mActiveSaySounds.end()) { - SoundLoudnessPair sndlist = sayiter->second; + MWBase::SoundStreamPtr stream = sayiter->second; mActiveSaySounds.erase(sayiter); - mActiveSaySounds[updated] = sndlist; - } - SayDecoderMap::iterator penditer = mPendingSaySounds.find(old); - if(penditer != mPendingSaySounds.end()) - { - DecoderLoudnessPair dl = penditer->second; - mPendingSaySounds.erase(penditer); - mPendingSaySounds[updated] = dl; + mActiveSaySounds[updated] = stream; } } @@ -1175,13 +1098,12 @@ namespace MWSound mActiveSounds.clear(); SaySoundMap::iterator sayiter = mActiveSaySounds.begin(); for(;sayiter != mActiveSaySounds.end();++sayiter) - mOutput->finishStream(sayiter->second.first); + mOutput->finishStream(sayiter->second); mActiveSaySounds.clear(); TrackList::iterator trkiter = mActiveTracks.begin(); for(;trkiter != mActiveTracks.end();++trkiter) mOutput->finishStream(*trkiter); mActiveTracks.clear(); - mPendingSaySounds.clear(); mUnderwaterSound.reset(); stopMusic(); } diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index 695ca92a2a..d499dce7d4 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -11,7 +11,6 @@ #include -#include "loudness.hpp" #include "../mwbase/soundmanager.hpp" namespace VFS @@ -69,12 +68,6 @@ namespace MWSound typedef std::map NameBufferMap; NameBufferMap mBufferNameMap; - typedef std::deque LoudnessList; - LoudnessList mVoiceLipBuffers; - - typedef std::map NameLoudnessRefMap; - NameLoudnessRefMap mVoiceLipNameMap; - // NOTE: unused buffers are stored in front-newest order. typedef std::deque SoundList; SoundList mUnusedBuffers; @@ -84,14 +77,9 @@ namespace MWSound typedef std::map SoundMap; SoundMap mActiveSounds; - typedef std::pair SoundLoudnessPair; - typedef std::map SaySoundMap; + typedef std::map SaySoundMap; SaySoundMap mActiveSaySounds; - typedef std::pair DecoderLoudnessPair; - typedef std::map SayDecoderMap; - SayDecoderMap mPendingSaySounds; - typedef std::vector TrackList; TrackList mActiveTracks; @@ -112,9 +100,8 @@ namespace MWSound Sound_Buffer *lookupSound(const std::string &soundId) const; Sound_Buffer *loadSound(const std::string &soundId); - // Ensures the loudness/"lip" data gets loaded, and returns a decoder - // to start streaming - DecoderPtr loadVoice(const std::string &voicefile, Sound_Loudness **lipdata); + // returns a decoder to start streaming + DecoderPtr loadVoice(const std::string &voicefile); MWBase::SoundStreamPtr playVoice(DecoderPtr decoder, const osg::Vec3f &pos, bool playlocal); diff --git a/apps/openmw/mwstate/charactermanager.cpp b/apps/openmw/mwstate/charactermanager.cpp index 22192c355d..90b25ab5af 100644 --- a/apps/openmw/mwstate/charactermanager.cpp +++ b/apps/openmw/mwstate/charactermanager.cpp @@ -32,11 +32,8 @@ MWState::CharacterManager::CharacterManager (const boost::filesystem::path& save } } -MWState::Character *MWState::CharacterManager::getCurrentCharacter (bool create, const std::string& name) +MWState::Character *MWState::CharacterManager::getCurrentCharacter () { - if (!mCurrent && create) - createCharacter(name); - return mCurrent; } @@ -56,7 +53,7 @@ void MWState::CharacterManager::deleteSlot(const MWState::Character *character, } } -void MWState::CharacterManager::createCharacter(const std::string& name) +MWState::Character* MWState::CharacterManager::createCharacter(const std::string& name) { std::ostringstream stream; @@ -82,8 +79,7 @@ void MWState::CharacterManager::createCharacter(const std::string& name) } mCharacters.push_back (Character (path, mGame)); - - mCurrent = &mCharacters.back(); + return &mCharacters.back(); } std::list::iterator MWState::CharacterManager::findCharacter(const MWState::Character* character) @@ -101,15 +97,16 @@ std::list::iterator MWState::CharacterManager::findCharacter void MWState::CharacterManager::setCurrentCharacter (const Character *character) { - std::list::iterator it = findCharacter(character); + if (!character) + mCurrent = NULL; + else + { + std::list::iterator it = findCharacter(character); - mCurrent = &*it; + mCurrent = &*it; + } } -void MWState::CharacterManager::clearCurrentCharacter() -{ - mCurrent = 0; -} std::list::const_iterator MWState::CharacterManager::begin() const { diff --git a/apps/openmw/mwstate/charactermanager.hpp b/apps/openmw/mwstate/charactermanager.hpp index c44c10b5a2..2daf73401f 100644 --- a/apps/openmw/mwstate/charactermanager.hpp +++ b/apps/openmw/mwstate/charactermanager.hpp @@ -31,20 +31,17 @@ namespace MWState CharacterManager (const boost::filesystem::path& saves, const std::string& game); - Character *getCurrentCharacter (bool create, const std::string& name); - ///< \param create Create a new character, if there is no current character. - /// \param name The character name to use in case a new character is created. + Character *getCurrentCharacter (); + ///< @note May return null void deleteSlot(const MWState::Character *character, const MWState::Slot *slot); - void createCharacter(const std::string& name); + Character* createCharacter(const std::string& name); ///< Create new character within saved game management /// \param name Name for the character (does not need to be unique) void setCurrentCharacter (const Character *character); - void clearCurrentCharacter(); - std::list::const_iterator begin() const; std::list::const_iterator end() const; diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 89d31c4853..a8639b94b5 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -54,7 +54,7 @@ void MWState::StateManager::cleanup (bool force) MWBase::Environment::get().getMechanicsManager()->clear(); mState = State_NoGame; - mCharacterManager.clearCurrentCharacter(); + mCharacterManager.setCurrentCharacter(NULL); mTimePlayed = 0; MWMechanics::CreatureStats::cleanup(); @@ -109,13 +109,14 @@ void MWState::StateManager::askLoadRecent() if( !mAskLoadRecent ) { - if(getCurrentCharacter()->begin() == getCurrentCharacter()->end() )//no saves + const MWState::Character* character = getCurrentCharacter(); + if(!character || character->begin() == character->end())//no saves { MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); } else { - MWState::Slot lastSave = *getCurrentCharacter()->begin(); + MWState::Slot lastSave = *character->begin(); std::vector buttons; buttons.push_back("#{sYes}"); buttons.push_back("#{sNo}"); @@ -172,8 +173,19 @@ void MWState::StateManager::endGame() void MWState::StateManager::saveGame (const std::string& description, const Slot *slot) { + MWState::Character* character = getCurrentCharacter(); + try { + if (!character) + { + MWWorld::ConstPtr player = MWMechanics::getPlayer(); + std::string name = player.get()->mBase->mName; + + character = mCharacterManager.createCharacter(name); + mCharacterManager.setCurrentCharacter(character); + } + ESM::SavedGame profile; MWBase::World& world = *MWBase::Environment::get().getWorld(); @@ -203,9 +215,9 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot writeScreenshot(profile.mScreenshot); if (!slot) - slot = getCurrentCharacter()->createSlot (profile); + slot = character->createSlot (profile); else - slot = getCurrentCharacter()->updateSlot (slot, profile); + slot = character->updateSlot (slot, profile); // Write to a memory stream first. If there is an exception during the save process, we don't want to trash the // existing save file we are overwriting. @@ -288,8 +300,11 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot MWBase::Environment::get().getWindowManager()->interactiveMessageBox(error.str(), buttons); // If no file was written, clean up the slot - if (slot && !boost::filesystem::exists(slot->mPath)) - getCurrentCharacter()->deleteSlot(slot); + if (character && slot && !boost::filesystem::exists(slot->mPath)) + { + character->deleteSlot(slot); + character->cleanup(); + } } } @@ -305,13 +320,16 @@ void MWState::StateManager::quickSave (std::string name) } const Slot* slot = NULL; - Character* mCurrentCharacter = getCurrentCharacter(true); //Get current character + Character* currentCharacter = getCurrentCharacter(); //Get current character //Find quicksave slot - for (Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it) + if (currentCharacter) { - if (it->mProfile.mDescription == name) - slot = &*it; + for (Character::SlotIterator it = currentCharacter->begin(); it != currentCharacter->end(); ++it) + { + if (it->mProfile.mDescription == name) + slot = &*it; + } } saveGame(name, slot); @@ -333,19 +351,8 @@ void MWState::StateManager::loadGame(const std::string& filepath) } } - // have to peek into the save file to get the player name - ESM::ESMReader reader; - reader.open (filepath); - if (reader.getRecName()!=ESM::REC_SAVE) - return; // invalid save file -> ignore - reader.getRecHeader(); - ESM::SavedGame profile; - profile.load (reader); - reader.close(); - - MWState::Character* character = mCharacterManager.getCurrentCharacter(true, profile.mPlayerName); + MWState::Character* character = getCurrentCharacter(); loadGame(character, filepath); - mTimePlayed = profile.mTimePlayed; } void MWState::StateManager::loadGame (const Character *character, const std::string& filepath) @@ -378,7 +385,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str ESM::NAME n = reader.getRecName(); reader.getRecHeader(); - switch (n.val) + switch (n.intval) { case ESM::REC_SAVE: { @@ -398,12 +405,12 @@ void MWState::StateManager::loadGame (const Character *character, const std::str case ESM::REC_JOUR_LEGACY: case ESM::REC_QUES: - MWBase::Environment::get().getJournal()->readRecord (reader, n.val); + MWBase::Environment::get().getJournal()->readRecord (reader, n.intval); break; case ESM::REC_DIAS: - MWBase::Environment::get().getDialogueManager()->readRecord (reader, n.val); + MWBase::Environment::get().getDialogueManager()->readRecord (reader, n.intval); break; case ESM::REC_ALCH: @@ -426,7 +433,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str case ESM::REC_ENAB: case ESM::REC_LEVC: case ESM::REC_LEVI: - MWBase::Environment::get().getWorld()->readRecord(reader, n.val, contentFileMap); + MWBase::Environment::get().getWorld()->readRecord(reader, n.intval, contentFileMap); break; case ESM::REC_CAM_: @@ -435,7 +442,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str case ESM::REC_GSCR: - MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord (reader, n.val); + MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord (reader, n.intval); break; case ESM::REC_GMAP: @@ -443,13 +450,13 @@ void MWState::StateManager::loadGame (const Character *character, const std::str case ESM::REC_ASPL: case ESM::REC_MARK: - MWBase::Environment::get().getWindowManager()->readRecord(reader, n.val); + MWBase::Environment::get().getWindowManager()->readRecord(reader, n.intval); break; case ESM::REC_DCOU: case ESM::REC_STLN: - MWBase::Environment::get().getMechanicsManager()->readRecord(reader, n.val); + MWBase::Environment::get().getMechanicsManager()->readRecord(reader, n.intval); break; default: @@ -470,7 +477,8 @@ void MWState::StateManager::loadGame (const Character *character, const std::str mState = State_Running; - Settings::Manager::setString ("character", "Saves", + if (character) + Settings::Manager::setString ("character", "Saves", character->getPath().filename().string()); MWBase::Environment::get().getWindowManager()->setNewGame(false); @@ -484,16 +492,35 @@ void MWState::StateManager::loadGame (const Character *character, const std::str MWWorld::ConstPtr ptr = MWMechanics::getPlayer(); - const ESM::CellId& cellId = ptr.getCell()->getCell()->getCellId(); + if (ptr.isInCell()) + { + const ESM::CellId& cellId = ptr.getCell()->getCell()->getCellId(); - // Use detectWorldSpaceChange=false, otherwise some of the data we just loaded would be cleared again - MWBase::Environment::get().getWorld()->changeToCell (cellId, ptr.getRefData().getPosition(), false); + // Use detectWorldSpaceChange=false, otherwise some of the data we just loaded would be cleared again + MWBase::Environment::get().getWorld()->changeToCell (cellId, ptr.getRefData().getPosition(), false, false); + } + else + { + // Cell no longer exists (i.e. changed game files), choose a default cell + MWWorld::CellStore* cell = MWBase::Environment::get().getWorld()->getExterior(0,0); + float x,y; + MWBase::Environment::get().getWorld()->indexToPosition(0,0,x,y,false); + ESM::Position pos; + pos.pos[0] = x; + pos.pos[1] = y; + pos.pos[2] = 0; // should be adjusted automatically (adjustPlayerPos=true) + pos.rot[0] = 0; + pos.rot[1] = 0; + pos.rot[2] = 0; + MWBase::Environment::get().getWorld()->changeToCell(cell->getCell()->getCellId(), pos, true, false); + } // Vanilla MW will restart startup scripts when a save game is loaded. This is unintuitive, // but some mods may be using it as a reload detector. MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup(); - // Do not trigger erroneous cellChanged events + // Since we passed "changeEvent=false" to changeCell, we shouldn't have triggered the cell change flag. + // But make sure the flag is cleared anyway in case it was set from an earlier game. MWBase::Environment::get().getWorld()->markCellAsUnchanged(); } catch (const std::exception& e) @@ -514,11 +541,11 @@ void MWState::StateManager::loadGame (const Character *character, const std::str void MWState::StateManager::quickLoad() { - if (Character* mCurrentCharacter = getCurrentCharacter (false)) + if (Character* currentCharacter = getCurrentCharacter ()) { - if (mCurrentCharacter->begin() == mCurrentCharacter->end()) + if (currentCharacter->begin() == currentCharacter->end()) return; - loadGame (mCurrentCharacter, mCurrentCharacter->begin()->mPath.string()); //Get newest save + loadGame (currentCharacter, currentCharacter->begin()->mPath.string()); //Get newest save } } @@ -527,12 +554,9 @@ void MWState::StateManager::deleteGame(const MWState::Character *character, cons mCharacterManager.deleteSlot(character, slot); } -MWState::Character *MWState::StateManager::getCurrentCharacter (bool create) +MWState::Character *MWState::StateManager::getCurrentCharacter () { - MWWorld::ConstPtr player = MWMechanics::getPlayer(); - std::string name = player.get()->mBase->mName; - - return mCharacterManager.getCurrentCharacter (create, name); + return mCharacterManager.getCurrentCharacter(); } MWState::StateManager::CharacterIterator MWState::StateManager::characterBegin() @@ -553,7 +577,7 @@ void MWState::StateManager::update (float duration) if (mAskLoadRecent) { int iButton = MWBase::Environment::get().getWindowManager()->readPressedButton(); - MWState::Character *curCharacter = getCurrentCharacter(false); + MWState::Character *curCharacter = getCurrentCharacter(); if(iButton==0 && curCharacter) { mAskLoadRecent = false; diff --git a/apps/openmw/mwstate/statemanagerimp.hpp b/apps/openmw/mwstate/statemanagerimp.hpp index 59dc919d14..5defcdea30 100644 --- a/apps/openmw/mwstate/statemanagerimp.hpp +++ b/apps/openmw/mwstate/statemanagerimp.hpp @@ -73,8 +73,8 @@ namespace MWState virtual void loadGame (const Character *character, const std::string &filepath); ///< Load a saved game file belonging to the given character. - virtual Character *getCurrentCharacter (bool create = true); - ///< \param create Create a new character, if there is no current character. + virtual Character *getCurrentCharacter (); + ///< @note May return null. virtual CharacterIterator characterBegin(); ///< Any call to SaveGame and getCurrentCharacter can invalidate the returned diff --git a/apps/openmw/mwworld/action.hpp b/apps/openmw/mwworld/action.hpp index 3e0e8ad1bd..38907cf442 100644 --- a/apps/openmw/mwworld/action.hpp +++ b/apps/openmw/mwworld/action.hpp @@ -32,6 +32,9 @@ namespace MWWorld virtual ~Action(); + virtual bool isNullAction() { return false; } + ///< Is running this action a no-op? (default false) + void execute (const Ptr& actor); void setSound (const std::string& id); diff --git a/apps/openmw/mwworld/actionteleport.cpp b/apps/openmw/mwworld/actionteleport.cpp index 031f07258c..c9315283dc 100644 --- a/apps/openmw/mwworld/actionteleport.cpp +++ b/apps/openmw/mwworld/actionteleport.cpp @@ -64,9 +64,9 @@ namespace MWWorld { world->getPlayer().setTeleported(true); if (mCellName.empty()) - world->changeToExteriorCell (mPosition); + world->changeToExteriorCell (mPosition, true); else - world->changeToInteriorCell (mCellName, mPosition); + world->changeToInteriorCell (mCellName, mPosition, true); } else { diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp new file mode 100644 index 0000000000..9f613ac80d --- /dev/null +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -0,0 +1,289 @@ +#include "cellpreloader.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include "../mwworld/inventorystore.hpp" +#include "../mwworld/esmstore.hpp" + +#include "cellstore.hpp" +#include "manualref.hpp" +#include "class.hpp" + +namespace MWWorld +{ + + struct ListModelsVisitor + { + ListModelsVisitor(std::vector& out) + : mOut(out) + { + } + + virtual bool operator()(const MWWorld::Ptr& ptr) + { + ptr.getClass().getModelsToPreload(ptr, mOut); + + return true; + } + + std::vector& mOut; + }; + + /// Worker thread item: preload models in a cell. + class PreloadItem : public SceneUtil::WorkItem + { + public: + /// Constructor to be called from the main thread. + PreloadItem(MWWorld::CellStore* cell, Resource::SceneManager* sceneManager, Resource::BulletShapeManager* bulletShapeManager, Resource::KeyframeManager* keyframeManager, Terrain::World* terrain, bool preloadInstances) + : mIsExterior(cell->getCell()->isExterior()) + , mX(cell->getCell()->getGridX()) + , mY(cell->getCell()->getGridY()) + , mSceneManager(sceneManager) + , mBulletShapeManager(bulletShapeManager) + , mKeyframeManager(keyframeManager) + , mTerrain(terrain) + , mPreloadInstances(preloadInstances) + { + ListModelsVisitor visitor (mMeshes); + if (cell->getState() == MWWorld::CellStore::State_Loaded) + { + cell->forEach(visitor); + } + else + { + const std::vector& objectIds = cell->getPreloadedIds(); + + // could possibly build the model list in the worker thread if we manage to make the Store thread safe + for (std::vector::const_iterator it = objectIds.begin(); it != objectIds.end(); ++it) + { + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), *it); + std::string model = ref.getPtr().getClass().getModel(ref.getPtr()); + if (!model.empty()) + mMeshes.push_back(model); + } + } + } + + /// Preload work to be called from the worker thread. + virtual void doWork() + { + for (MeshList::const_iterator it = mMeshes.begin(); it != mMeshes.end(); ++it) + { + try + { + std::string mesh = *it; + mesh = Misc::ResourceHelpers::correctActorModelPath(mesh, mSceneManager->getVFS()); + + if (mPreloadInstances) + { + mPreloadedObjects.push_back(mSceneManager->cacheInstance(mesh)); + mPreloadedObjects.push_back(mBulletShapeManager->cacheInstance(mesh)); + } + else + { + mPreloadedObjects.push_back(mSceneManager->getTemplate(mesh)); + mPreloadedObjects.push_back(mBulletShapeManager->getShape(mesh)); + } + + size_t slashpos = mesh.find_last_of("/\\"); + if (slashpos != std::string::npos && slashpos != mesh.size()-1) + { + Misc::StringUtils::lowerCaseInPlace(mesh); + if (mesh[slashpos+1] == 'x') + { + std::string kfname = mesh; + if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0) + { + kfname.replace(kfname.size()-4, 4, ".kf"); + mPreloadedObjects.push_back(mKeyframeManager->get(kfname)); + } + + } + } + } + catch (std::exception& e) + { + // ignore error for now, would spam the log too much + // error will be shown when visiting the cell + } + } + + if (mIsExterior) + { + try + { + mPreloadedObjects.push_back(mTerrain->cacheCell(mX, mY)); + } + catch(std::exception& e) + { + } + } + } + + private: + typedef std::vector MeshList; + bool mIsExterior; + int mX; + int mY; + MeshList mMeshes; + Resource::SceneManager* mSceneManager; + Resource::BulletShapeManager* mBulletShapeManager; + Resource::KeyframeManager* mKeyframeManager; + Terrain::World* mTerrain; + bool mPreloadInstances; + + // keep a ref to the loaded objects to make sure it stays loaded as long as this cell is in the preloaded state + std::vector > mPreloadedObjects; + }; + + /// Worker thread item: update the resource system's cache, effectively deleting unused entries. + class UpdateCacheItem : public SceneUtil::WorkItem + { + public: + UpdateCacheItem(Resource::ResourceSystem* resourceSystem, Terrain::World* terrain, double referenceTime) + : mReferenceTime(referenceTime) + , mResourceSystem(resourceSystem) + , mTerrain(terrain) + { + } + + virtual void doWork() + { + mResourceSystem->updateCache(mReferenceTime); + + mTerrain->updateCache(); + } + + private: + double mReferenceTime; + Resource::ResourceSystem* mResourceSystem; + Terrain::World* mTerrain; + }; + + CellPreloader::CellPreloader(Resource::ResourceSystem* resourceSystem, Resource::BulletShapeManager* bulletShapeManager, Terrain::World* terrain) + : mResourceSystem(resourceSystem) + , mBulletShapeManager(bulletShapeManager) + , mTerrain(terrain) + , mExpiryDelay(0.0) + , mMinCacheSize(0) + , mMaxCacheSize(0) + , mPreloadInstances(true) + { + } + + CellPreloader::~CellPreloader() + { + for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end();++it) + it->second.mWorkItem->waitTillDone(); + mPreloadCells.clear(); + } + + void CellPreloader::preload(CellStore *cell, double timestamp) + { + if (!mWorkQueue) + { + std::cerr << "can't preload, no work queue set " << std::endl; + return; + } + if (cell->getState() == CellStore::State_Unloaded) + { + std::cerr << "can't preload objects for unloaded cell" << std::endl; + return; + } + + PreloadMap::iterator found = mPreloadCells.find(cell); + if (found != mPreloadCells.end()) + { + // already preloaded, nothing to do other than updating the timestamp + found->second.mTimeStamp = timestamp; + return; + } + + while (mPreloadCells.size() >= mMaxCacheSize) + { + // throw out oldest cell to make room + PreloadMap::iterator oldestCell = mPreloadCells.begin(); + double oldestTimestamp = DBL_MAX; + double threshold = 1.0; // seconds + for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end(); ++it) + { + if (it->second.mTimeStamp < oldestTimestamp) + { + oldestTimestamp = it->second.mTimeStamp; + oldestCell = it; + } + } + + if (oldestTimestamp + threshold < timestamp) + mPreloadCells.erase(oldestCell); + else + return; + } + + osg::ref_ptr item (new PreloadItem(cell, mResourceSystem->getSceneManager(), mBulletShapeManager, mResourceSystem->getKeyframeManager(), mTerrain, mPreloadInstances)); + mWorkQueue->addWorkItem(item); + + mPreloadCells[cell] = PreloadEntry(timestamp, item); + } + + void CellPreloader::notifyLoaded(CellStore *cell) + { + mPreloadCells.erase(cell); + } + + void CellPreloader::updateCache(double timestamp) + { + for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end();) + { + if (mPreloadCells.size() >= mMinCacheSize && it->second.mTimeStamp < timestamp - mExpiryDelay) + mPreloadCells.erase(it++); + else + ++it; + } + + // the resource cache is cleared from the worker thread so that we're not holding up the main thread with delete operations + mWorkQueue->addWorkItem(new UpdateCacheItem(mResourceSystem, mTerrain, timestamp), true); + } + + void CellPreloader::setExpiryDelay(double expiryDelay) + { + mExpiryDelay = expiryDelay; + } + + void CellPreloader::setMinCacheSize(unsigned int num) + { + mMinCacheSize = num; + } + + void CellPreloader::setMaxCacheSize(unsigned int num) + { + mMaxCacheSize = num; + } + + void CellPreloader::setPreloadInstances(bool preload) + { + mPreloadInstances = preload; + } + + unsigned int CellPreloader::getMaxCacheSize() const + { + return mMaxCacheSize; + } + + void CellPreloader::setWorkQueue(osg::ref_ptr workQueue) + { + mWorkQueue = workQueue; + } + +} diff --git a/apps/openmw/mwworld/cellpreloader.hpp b/apps/openmw/mwworld/cellpreloader.hpp new file mode 100644 index 0000000000..35c52baf60 --- /dev/null +++ b/apps/openmw/mwworld/cellpreloader.hpp @@ -0,0 +1,87 @@ +#ifndef OPENMW_MWWORLD_CELLPRELOADER_H +#define OPENMW_MWWORLD_CELLPRELOADER_H + +#include +#include +#include + +namespace Resource +{ + class ResourceSystem; + class BulletShapeManager; +} + +namespace Terrain +{ + class World; +} + +namespace MWWorld +{ + class CellStore; + + class CellPreloader + { + public: + CellPreloader(Resource::ResourceSystem* resourceSystem, Resource::BulletShapeManager* bulletShapeManager, Terrain::World* terrain); + ~CellPreloader(); + + /// Ask a background thread to preload rendering meshes and collision shapes for objects in this cell. + /// @note The cell itself must be in State_Loaded or State_Preloaded. + void preload(MWWorld::CellStore* cell, double timestamp); + + void notifyLoaded(MWWorld::CellStore* cell); + + /// Removes preloaded cells that have not had a preload request for a while. + void updateCache(double timestamp); + + /// How long to keep a preloaded cell in cache after it's no longer requested. + void setExpiryDelay(double expiryDelay); + + /// The minimum number of preloaded cells before unused cells get thrown out. + void setMinCacheSize(unsigned int num); + + /// The maximum number of preloaded cells. + void setMaxCacheSize(unsigned int num); + + /// Enables the creation of instances in the preloading thread. + void setPreloadInstances(bool preload); + + unsigned int getMaxCacheSize() const; + + void setWorkQueue(osg::ref_ptr workQueue); + + private: + Resource::ResourceSystem* mResourceSystem; + Resource::BulletShapeManager* mBulletShapeManager; + Terrain::World* mTerrain; + osg::ref_ptr mWorkQueue; + double mExpiryDelay; + unsigned int mMinCacheSize; + unsigned int mMaxCacheSize; + bool mPreloadInstances; + + struct PreloadEntry + { + PreloadEntry(double timestamp, osg::ref_ptr workItem) + : mTimeStamp(timestamp) + , mWorkItem(workItem) + { + } + PreloadEntry() + : mTimeStamp(0.0) + { + } + + double mTimeStamp; + osg::ref_ptr mWorkItem; + }; + typedef std::map PreloadMap; + + // Cells that are currently being preloaded, or have already finished preloading + PreloadMap mPreloadCells; + }; + +} + +#endif diff --git a/apps/openmw/mwworld/cells.cpp b/apps/openmw/mwworld/cells.cpp index 75d908db83..384d7e1242 100644 --- a/apps/openmw/mwworld/cells.cpp +++ b/apps/openmw/mwworld/cells.cpp @@ -105,6 +105,10 @@ MWWorld::CellStore *MWWorld::Cells::getExterior (int x, int y) { // Cell isn't predefined. Make one on the fly. ESM::Cell record; + record.mCellId.mWorldspace = ESM::CellId::sDefaultWorldspace; + record.mCellId.mPaged = true; + record.mCellId.mIndex.mX = x; + record.mCellId.mIndex.mY = y; record.mData.mFlags = ESM::Cell::HasWater; record.mData.mX = x; diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 08f272ea2a..1ca452efe2 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -194,7 +194,7 @@ namespace MWWorld else { std::cerr - << "Error: could not resolve cell reference " << ref.mRefID + << "Error: could not resolve cell reference '" << ref.mRefID << "'" << " (dropping reference)" << std::endl; } } @@ -333,6 +333,11 @@ namespace MWWorld return mState; } + const std::vector &CellStore::getPreloadedIds() const + { + return mIds; + } + bool CellStore::hasState() const { return mHasState; @@ -458,27 +463,34 @@ namespace MWWorld // Load references from all plugins that do something with this cell. for (size_t i = 0; i < mCell->mContextList.size(); i++) { - // Reopen the ESM reader and seek to the right position. - int index = mCell->mContextList.at(i).index; - mCell->restore (esm[index], i); - - ESM::CellRef ref; - - // Get each reference in turn - bool deleted = false; - while (mCell->getNextRef (esm[index], ref, deleted)) + try { - if (deleted) - continue; + // Reopen the ESM reader and seek to the right position. + int index = mCell->mContextList.at(i).index; + mCell->restore (esm[index], i); - // Don't list reference if it was moved to a different cell. - ESM::MovedCellRefTracker::const_iterator iter = - std::find(mCell->mMovedRefs.begin(), mCell->mMovedRefs.end(), ref.mRefNum); - if (iter != mCell->mMovedRefs.end()) { - continue; + ESM::CellRef ref; + + // Get each reference in turn + bool deleted = false; + while (mCell->getNextRef (esm[index], ref, deleted)) + { + if (deleted) + continue; + + // Don't list reference if it was moved to a different cell. + ESM::MovedCellRefTracker::const_iterator iter = + std::find(mCell->mMovedRefs.begin(), mCell->mMovedRefs.end(), ref.mRefNum); + if (iter != mCell->mMovedRefs.end()) { + continue; + } + + mIds.push_back (Misc::StringUtils::lowerCase (ref.mRefID)); } - - mIds.push_back (Misc::StringUtils::lowerCase (ref.mRefID)); + } + catch (std::exception& e) + { + std::cerr << "An error occurred listing references for cell " << getCell()->getDescription() << ": " << e.what() << std::endl; } } @@ -505,25 +517,32 @@ namespace MWWorld // Load references from all plugins that do something with this cell. for (size_t i = 0; i < mCell->mContextList.size(); i++) { - // Reopen the ESM reader and seek to the right position. - int index = mCell->mContextList.at(i).index; - mCell->restore (esm[index], i); - - ESM::CellRef ref; - ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile; - - // Get each reference in turn - bool deleted = false; - while(mCell->getNextRef(esm[index], ref, deleted)) + try { - // Don't load reference if it was moved to a different cell. - ESM::MovedCellRefTracker::const_iterator iter = - std::find(mCell->mMovedRefs.begin(), mCell->mMovedRefs.end(), ref.mRefNum); - if (iter != mCell->mMovedRefs.end()) { - continue; - } + // Reopen the ESM reader and seek to the right position. + int index = mCell->mContextList.at(i).index; + mCell->restore (esm[index], i); - loadRef (ref, deleted); + ESM::CellRef ref; + ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile; + + // Get each reference in turn + bool deleted = false; + while(mCell->getNextRef(esm[index], ref, deleted)) + { + // Don't load reference if it was moved to a different cell. + ESM::MovedCellRefTracker::const_iterator iter = + std::find(mCell->mMovedRefs.begin(), mCell->mMovedRefs.end(), ref.mRefNum); + if (iter != mCell->mMovedRefs.end()) { + continue; + } + + loadRef (ref, deleted); + } + } + catch (std::exception& e) + { + std::cerr << "An error occurred loading references for cell " << getCell()->getDescription() << ": " << e.what() << std::endl; } } @@ -591,8 +610,9 @@ namespace MWWorld case ESM::REC_REPA: mRepairs.load(ref, deleted, store); break; case ESM::REC_STAT: mStatics.load(ref, deleted, store); break; case ESM::REC_WEAP: mWeapons.load(ref, deleted, store); break; + case ESM::REC_BODY: mBodyParts.load(ref, deleted, store); break; - case 0: std::cerr << "Cell reference " + ref.mRefID + " not found!\n"; break; + case 0: std::cerr << "Cell reference '" + ref.mRefID + "' not found!\n"; break; default: std::cerr @@ -659,6 +679,7 @@ namespace MWWorld writeReferenceCollection (writer, mRepairs); writeReferenceCollection (writer, mStatics); writeReferenceCollection (writer, mWeapons); + writeReferenceCollection (writer, mBodyParts); for (MovedRefTracker::const_iterator it = mMovedToAnotherCell.begin(); it != mMovedToAnotherCell.end(); ++it) { @@ -794,12 +815,21 @@ namespace MWWorld readReferenceCollection (reader, mWeapons, cref, contentFileMap); break; + case ESM::REC_BODY: + + readReferenceCollection (reader, mBodyParts, cref, contentFileMap); + break; + default: throw std::runtime_error ("unknown type in cell reference section"); } } + // Do another update here to make sure objects referred to by MVRF tags can be found + // This update is only needed for old saves that used the old copy&delete way of moving objects + updateMergedRefs(); + while (reader.isNextSub("MVRF")) { reader.cacheSubName(); @@ -841,8 +871,6 @@ namespace MWWorld moveTo(MWWorld::Ptr(movedRef, this), otherCell); } - - updateMergedRefs(); } bool operator== (const CellStore& left, const CellStore& right) @@ -875,11 +903,19 @@ namespace MWWorld return mFogState.get(); } + void clearCorpse(const MWWorld::Ptr& ptr) + { + const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); + static const float fCorpseClearDelay = MWBase::Environment::get().getWorld()->getStore().get().find("fCorpseClearDelay")->getFloat(); + if (creatureStats.isDead() && !ptr.getClass().isPersistent(ptr) && creatureStats.getTimeOfDeath() + fCorpseClearDelay <= MWBase::Environment::get().getWorld()->getTimeStamp()) + MWBase::Environment::get().getWorld()->deleteObject(ptr); + } + void CellStore::respawn() { if (mState == State_Loaded) { - static int iMonthsToRespawn = MWBase::Environment::get().getWorld()->getStore().get().find("iMonthsToRespawn")->getInt(); + static const int iMonthsToRespawn = MWBase::Environment::get().getWorld()->getStore().get().find("iMonthsToRespawn")->getInt(); if (MWBase::Environment::get().getWorld()->getTimeStamp() - mLastRespawn > 24*30*iMonthsToRespawn) { mLastRespawn = MWBase::Environment::get().getWorld()->getTimeStamp(); @@ -888,21 +924,25 @@ namespace MWWorld Ptr ptr (&*it, this); ptr.getClass().respawn(ptr); } - for (CellRefList::List::iterator it (mCreatures.mList.begin()); it!=mCreatures.mList.end(); ++it) - { - Ptr ptr (&*it, this); - ptr.getClass().respawn(ptr); - } - for (CellRefList::List::iterator it (mNpcs.mList.begin()); it!=mNpcs.mList.end(); ++it) - { - Ptr ptr (&*it, this); - ptr.getClass().respawn(ptr); - } - for (CellRefList::List::iterator it (mCreatureLists.mList.begin()); it!=mCreatureLists.mList.end(); ++it) - { - Ptr ptr (&*it, this); - ptr.getClass().respawn(ptr); - } + } + + for (CellRefList::List::iterator it (mCreatures.mList.begin()); it!=mCreatures.mList.end(); ++it) + { + Ptr ptr (&*it, this); + clearCorpse(ptr); + ptr.getClass().respawn(ptr); + } + for (CellRefList::List::iterator it (mNpcs.mList.begin()); it!=mNpcs.mList.end(); ++it) + { + Ptr ptr (&*it, this); + clearCorpse(ptr); + ptr.getClass().respawn(ptr); + } + for (CellRefList::List::iterator it (mCreatureLists.mList.begin()); it!=mCreatureLists.mList.end(); ++it) + { + Ptr ptr (&*it, this); + // no need to clearCorpse, handled as part of mCreatures + ptr.getClass().respawn(ptr); } } } diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 27fe9ec036..36cc7f5eb8 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -31,6 +31,7 @@ #include #include #include +#include #include "../mwmechanics/pathgrid.hpp" // TODO: maybe belongs in mwworld @@ -97,6 +98,7 @@ namespace MWWorld CellRefList mRepairs; CellRefList mStatics; CellRefList mWeapons; + CellRefList mBodyParts; typedef std::map MovedRefTracker; // References owned by a different cell that have been moved here. @@ -152,6 +154,7 @@ namespace MWWorld forEachImp (visitor, mRepairs) && forEachImp (visitor, mStatics) && forEachImp (visitor, mWeapons) && + forEachImp (visitor, mBodyParts) && forEachImp (visitor, mCreatures) && forEachImp (visitor, mNpcs) && forEachImp (visitor, mCreatureLists); @@ -201,6 +204,9 @@ namespace MWWorld State getState() const; + const std::vector& getPreloadedIds() const; + ///< Get Ids of objects in this cell, only valid in State_Preloaded + bool hasState() const; ///< Does this cell have state that needs to be stored in a saved game file? @@ -243,6 +249,7 @@ namespace MWWorld /// Call visitor (MWWorld::Ptr) for each reference. visitor must return a bool. Returning /// false will abort the iteration. /// \note Prefer using forEachConst when possible. + /// \note Do not modify this cell (i.e. remove/add objects) during the forEach, doing this may result in unintended behaviour. /// \attention This function also lists deleted (count 0) objects! /// \return Iteration completed? template @@ -251,6 +258,9 @@ namespace MWWorld if (mState != State_Loaded) return false; + if (mMergedRefs.empty()) + return true; + mHasState = true; for (unsigned int i=0; i @@ -288,6 +299,7 @@ namespace MWWorld /// Call visitor (ref) for each reference of given type. visitor must return a bool. Returning /// false will abort the iteration. + /// \note Do not modify this cell (i.e. remove/add objects) during the forEach, doing this may result in unintended behaviour. /// \attention This function also lists deleted (count 0) objects! /// \return Iteration completed? template @@ -296,6 +308,9 @@ namespace MWWorld if (mState != State_Loaded) return false; + if (mMergedRefs.empty()) + return true; + mHasState = true; CellRefList& list = get(); @@ -518,6 +533,13 @@ namespace MWWorld return mWeapons; } + template<> + inline CellRefList& CellStore::get() + { + mHasState = true; + return mBodyParts; + } + bool operator== (const CellStore& left, const CellStore& right); bool operator!= (const CellStore& left, const CellStore& right); } diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 7f2d759b95..ecfe7cb7c4 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -291,6 +291,13 @@ namespace MWWorld return ""; } + void Class::getModelsToPreload(const Ptr &ptr, std::vector &models) const + { + std::string model = getModel(ptr); + if (!model.empty()) + models.push_back(model); + } + std::string Class::applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const { throw std::runtime_error ("class can't be enchanted"); diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 85cd9ff7c5..0bb3edbeef 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -262,6 +262,9 @@ namespace MWWorld virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; + virtual void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const; + ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: list getModel(). + virtual std::string applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index ab9fa46110..7ac0d9d4ea 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -136,16 +136,18 @@ int MWWorld::ContainerStore::count(const std::string &id) return total; } -void MWWorld::ContainerStore::unstack(const Ptr &ptr, const Ptr& container) +MWWorld::ContainerStoreIterator MWWorld::ContainerStore::unstack(const Ptr &ptr, const Ptr& container, int count) { - if (ptr.getRefData().getCount() <= 1) - return; - MWWorld::ContainerStoreIterator it = addNewStack(ptr, ptr.getRefData().getCount()-1); + if (ptr.getRefData().getCount() <= count) + return end(); + MWWorld::ContainerStoreIterator it = addNewStack(ptr, ptr.getRefData().getCount()-count); const std::string script = it->getClass().getScript(*it); if (!script.empty()) MWBase::Environment::get().getWorld()->getLocalScripts().add(script, *it); - remove(ptr, ptr.getRefData().getCount()-1, container); + remove(ptr, ptr.getRefData().getCount()-count, container); + + return it; } MWWorld::ContainerStoreIterator MWWorld::ContainerStore::restack(const MWWorld::Ptr& item) @@ -529,10 +531,10 @@ void MWWorld::ContainerStore::restock (const ESM::InventoryList& items, const MW { std::map::iterator listInMap = allowedForReplace.find(itemOrList); - int restockNum = it->mCount; + int restockNum = std::abs(it->mCount); //If we know we must restock less, take it into account if(listInMap != allowedForReplace.end()) - restockNum += listInMap->second;//We add, because list items have negative count + restockNum -= std::min(restockNum, listInMap->second); //restock addInitialItem(itemOrList, owner, restockNum, true); } @@ -627,7 +629,7 @@ int MWWorld::ContainerStore::getType (const ConstPtr& ptr) return Type_Weapon; throw std::runtime_error ( - "Object of type " + ptr.getTypeName() + " can not be placed into a container"); + "Object '" + ptr.getCellRef().getRefId() + "' of type " + ptr.getTypeName() + " can not be placed into a container"); } MWWorld::Ptr MWWorld::ContainerStore::search (const std::string& id) diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index b7ec4bb74e..6a8fc47616 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -130,8 +130,10 @@ namespace MWWorld /// /// @return the number of items actually removed - void unstack (const Ptr& ptr, const Ptr& container); - ///< Unstack an item in this container. The item's count will be set to 1, then a new stack will be added with (origCount-1). + ContainerStoreIterator unstack (const Ptr& ptr, const Ptr& container, int count = 1); + ///< Unstack an item in this container. The item's count will be set to count, then a new stack will be added with (origCount-count). + /// + /// @return an iterator to the new stack, or end() if no new stack was created. MWWorld::ContainerStoreIterator restack (const MWWorld::Ptr& item); ///< Attempt to re-stack an item in this container. diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 1882c6e1aa..4a1763d0aa 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -19,7 +19,8 @@ static bool isCacheableRecord(int id) id == ESM::REC_BOOK || id == ESM::REC_CLOT || id == ESM::REC_CONT || id == ESM::REC_CREA || id == ESM::REC_DOOR || id == ESM::REC_INGR || id == ESM::REC_LEVC || id == ESM::REC_LEVI || id == ESM::REC_LIGH || id == ESM::REC_LOCK || id == ESM::REC_MISC || id == ESM::REC_NPC_ || - id == ESM::REC_PROB || id == ESM::REC_REPA || id == ESM::REC_STAT || id == ESM::REC_WEAP) + id == ESM::REC_PROB || id == ESM::REC_REPA || id == ESM::REC_STAT || id == ESM::REC_WEAP || + id == ESM::REC_BODY) { return true; } @@ -73,10 +74,10 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) esm.getRecHeader(); // Look up the record type. - std::map::iterator it = mStores.find(n.val); + std::map::iterator it = mStores.find(n.intval); if (it == mStores.end()) { - if (n.val == ESM::REC_INFO) { + if (n.intval == ESM::REC_INFO) { if (dialogue) { dialogue->readInfo(esm, esm.getIndex() != 0); @@ -86,12 +87,12 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) std::cerr << "error: info record without dialog" << std::endl; esm.skipRecord(); } - } else if (n.val == ESM::REC_MGEF) { + } else if (n.intval == ESM::REC_MGEF) { mMagicEffects.load (esm); - } else if (n.val == ESM::REC_SKIL) { + } else if (n.intval == ESM::REC_SKIL) { mSkills.load (esm); } - else if (n.val==ESM::REC_FILT || n.val == ESM::REC_DBGP) + else if (n.intval==ESM::REC_FILT || n.intval == ESM::REC_DBGP) { // ignore project file only records esm.skipRecord(); @@ -109,7 +110,7 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) continue; } - if (n.val==ESM::REC_DIAL) { + if (n.intval==ESM::REC_DIAL) { dialogue = const_cast(mDialogs.find(id.mId)); } else { dialogue = 0; diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index b82b798d11..e8a4d1c4d7 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -255,6 +255,9 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) // Equipping weapons is handled by AiCombat. Anything else (lockpicks, probes) can't be used by NPCs anyway (yet) continue; + if (iter.getType() == MWWorld::ContainerStore::Type_Weapon) + continue; + if (slots_.at (*iter2)!=end()) { Ptr old = *slots_.at (*iter2); @@ -345,6 +348,9 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) mMagicEffects = MWMechanics::MagicEffects(); + if (actor.getClass().getCreatureStats(actor).isDead()) + return; + for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) { if (*iter==end()) @@ -523,6 +529,9 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, const MWWorld::Ptr& actor) { + if (slot<0 || slot>=static_cast (mSlots.size())) + throw std::runtime_error ("slot number out of range"); + ContainerStoreIterator it = mSlots[slot]; if (it != end()) @@ -571,6 +580,33 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItem(const MWWor throw std::runtime_error ("attempt to unequip an item that is not currently equipped"); } +MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItemQuantity(const Ptr& item, const Ptr& actor, int count) +{ + if (!isEquipped(item)) + throw std::runtime_error ("attempt to unequip an item that is not currently equipped"); + if (count <= 0) + throw std::runtime_error ("attempt to unequip nothing (count <= 0)"); + if (count > item.getRefData().getCount()) + throw std::runtime_error ("attempt to unequip more items than equipped"); + + if (count == item.getRefData().getCount()) + return unequipItem(item, actor); + + // Move items to an existing stack if possible, otherwise split count items out into a new stack. + // Moving counts manually here, since ContainerStore's restack can't target unequipped stacks. + for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter) + { + if (stacks(*iter, item) && !isEquipped(*iter)) + { + iter->getRefData().setCount(iter->getRefData().getCount() + count); + item.getRefData().setCount(item.getRefData().getCount() - count); + return iter; + } + } + + return unstack(item, actor, item.getRefData().getCount() - count); +} + MWWorld::InventoryStoreListener* MWWorld::InventoryStore::getListener() { return mListener; @@ -686,7 +722,11 @@ void MWWorld::InventoryStore::rechargeItems(float duration) void MWWorld::InventoryStore::purgeEffect(short effectId) { - mMagicEffects.remove(MWMechanics::EffectKey(effectId)); + for (TSlots::const_iterator it = mSlots.begin(); it != mSlots.end(); ++it) + { + if (*it != end()) + purgeEffect(effectId, (*it)->getCellRef().getRefId()); + } } void MWWorld::InventoryStore::purgeEffect(short effectId, const std::string &sourceId) diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 28c44bcec1..2d7c9f6e9c 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -188,6 +188,15 @@ namespace MWWorld /// (it can be re-stacked so its count may be different than when it /// was equipped). + ContainerStoreIterator unequipItemQuantity(const Ptr& item, const Ptr& actor, int count); + ///< Unequip a specific quantity of an item identified by its Ptr. + /// An exception is thrown if the item is not currently equipped, + /// if count <= 0, or if count > the item stack size. + /// + /// @return an iterator to the unequipped items that were previously + /// in the slot (they can be re-stacked so its count may be different + /// than the requested count). + void setListener (InventoryStoreListener* listener, const Ptr& actor); ///< Set a listener for various events, see \a InventoryStoreListener diff --git a/apps/openmw/mwworld/localscripts.cpp b/apps/openmw/mwworld/localscripts.cpp index 46d0b3cc22..5f6d10b314 100644 --- a/apps/openmw/mwworld/localscripts.cpp +++ b/apps/openmw/mwworld/localscripts.cpp @@ -61,11 +61,9 @@ namespace } -MWWorld::LocalScripts::LocalScripts (const MWWorld::ESMStore& store) : mStore (store) {} - -void MWWorld::LocalScripts::setIgnore (const ConstPtr& ptr) +MWWorld::LocalScripts::LocalScripts (const MWWorld::ESMStore& store) : mStore (store) { - mIgnore = ptr; + mIter = mScripts.end(); } void MWWorld::LocalScripts::startIteration() @@ -73,32 +71,17 @@ void MWWorld::LocalScripts::startIteration() mIter = mScripts.begin(); } -bool MWWorld::LocalScripts::isFinished() const +bool MWWorld::LocalScripts::getNext(std::pair& script) { - if (mIter==mScripts.end()) - return true; - - if (!mIgnore.isEmpty() && mIter->second==mIgnore) + while (mIter!=mScripts.end()) { - std::list >::iterator iter = mIter; - return ++iter==mScripts.end(); + std::list >::iterator iter = mIter++; + script = *iter; + return true; } - return false; } -std::pair MWWorld::LocalScripts::getNext() -{ - assert (!isFinished()); - - std::list >::iterator iter = mIter++; - - if (mIgnore.isEmpty() || iter->second!=mIgnore) - return *iter; - - return getNext(); -} - void MWWorld::LocalScripts::add (const std::string& scriptName, const Ptr& ptr) { if (const ESM::Script *script = mStore.get().search (scriptName)) @@ -107,6 +90,14 @@ void MWWorld::LocalScripts::add (const std::string& scriptName, const Ptr& ptr) { ptr.getRefData().setLocals (*script); + for (std::list >::iterator iter = mScripts.begin(); iter!=mScripts.end(); ++iter) + if (iter->second==ptr) + { + std::cerr << "warning, tried to add local script twice for " << ptr.getCellRef().getRefId() << std::endl; + remove(ptr); + break; + } + mScripts.push_back (std::make_pair (scriptName, ptr)); } catch (const std::exception& exception) diff --git a/apps/openmw/mwworld/localscripts.hpp b/apps/openmw/mwworld/localscripts.hpp index 6ef4633a1d..c698daf043 100644 --- a/apps/openmw/mwworld/localscripts.hpp +++ b/apps/openmw/mwworld/localscripts.hpp @@ -17,25 +17,18 @@ namespace MWWorld { std::list > mScripts; std::list >::iterator mIter; - MWWorld::ConstPtr mIgnore; const MWWorld::ESMStore& mStore; public: LocalScripts (const MWWorld::ESMStore& store); - void setIgnore (const ConstPtr& ptr); - ///< Mark a single reference for ignoring during iteration over local scripts (will revoke - /// previous ignores). - void startIteration(); ///< Set the iterator to the begin of the script list. - bool isFinished() const; - ///< Is iteration finished? - - std::pair getNext(); - ///< Get next local script (must not be called if isFinished()) + bool getNext(std::pair& script); + ///< Get next local script + /// @return Did we get a script? void add (const std::string& scriptName, const Ptr& ptr); ///< Add script to collection of active local scripts. diff --git a/apps/openmw/mwworld/manualref.cpp b/apps/openmw/mwworld/manualref.cpp index d6e40ad09e..c683f7e036 100644 --- a/apps/openmw/mwworld/manualref.cpp +++ b/apps/openmw/mwworld/manualref.cpp @@ -54,6 +54,7 @@ MWWorld::ManualRef::ManualRef(const MWWorld::ESMStore& store, const std::string& case ESM::REC_REPA: create(store.get(), lowerName, mRef, mPtr); break; case ESM::REC_STAT: create(store.get(), lowerName, mRef, mPtr); break; case ESM::REC_WEAP: create(store.get(), lowerName, mRef, mPtr); break; + case ESM::REC_BODY: create(store.get(), lowerName, mRef, mPtr); break; case 0: throw std::logic_error("failed to create manual cell ref for " + lowerName + " (unknown ID)"); diff --git a/apps/openmw/mwworld/nullaction.hpp b/apps/openmw/mwworld/nullaction.hpp index 7ef8b4a065..5461d87112 100644 --- a/apps/openmw/mwworld/nullaction.hpp +++ b/apps/openmw/mwworld/nullaction.hpp @@ -9,6 +9,8 @@ namespace MWWorld class NullAction : public Action { virtual void executeImp (const Ptr& actor) {} + + virtual bool isNullAction() { return true; } }; } diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 19b66c3c93..0fcb2eb6d1 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -258,6 +258,11 @@ namespace MWWorld return MWBase::Environment::get().getMechanicsManager()->getActorsFighting(getPlayer()).size() != 0; } + bool Player::enemiesNearby() + { + return MWBase::Environment::get().getMechanicsManager()->getEnemiesNearby(getPlayer()).size() != 0; + } + void Player::markPosition(CellStore *markedCell, ESM::Position markedPosition) { mMarkedCell = markedCell; @@ -355,12 +360,8 @@ namespace MWWorld catch (...) { std::cerr << "Player cell '" << player.mCellId.mWorldspace << "' no longer exists" << std::endl; - // Cell no longer exists. Place the player in a default cell. - ESM::Position pos = mPlayer.mData.getPosition(); - MWBase::Environment::get().getWorld()->indexToPosition(0, 0, pos.pos[0], pos.pos[1], true); - pos.pos[2] = 0; - mPlayer.mData.setPosition(pos); - mCellStore = world.getExterior(0,0); + // Cell no longer exists. The loader will have to choose a default cell. + mCellStore = NULL; } if (!player.mBirthsign.empty()) diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index 595dd89fc6..1575018032 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -109,6 +109,8 @@ namespace MWWorld ///Checks all nearby actors to see if anyone has an aipackage against you bool isInCombat(); + bool enemiesNearby(); + void clear(); void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 439ac19ec9..90fba827f9 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -92,7 +92,7 @@ namespace MWWorld attachTo = rotateNode; } - mResourceSystem->getSceneManager()->createInstance(model, attachTo); + mResourceSystem->getSceneManager()->getInstance(model, attachTo); SceneUtil::DisableFreezeOnCullVisitor disableFreezeOnCullVisitor; state.mNode->accept(disableFreezeOnCullVisitor); @@ -117,7 +117,13 @@ namespace MWWorld const ESM::EffectList &effects, const Ptr &caster, const std::string &sourceName, const osg::Vec3f& fallbackDirection) { - osg::Vec3f pos = mPhysics->getPosition(caster) + osg::Vec3f(0,0,mPhysics->getHalfExtents(caster).z() * 0.5); // Spawn at 0.75 * ActorHeight + osg::Vec3f pos = caster.getRefData().getPosition().asVec3(); + if (caster.getClass().isActor()) + { + // Spawn at 0.75 * ActorHeight + // Note: we ignore the collision box offset, this is required to make some flying creatures work as intended. + pos.z() += mPhysics->getHalfExtents(caster).z() * 2 * 0.75; + } if (MWBase::Environment::get().getWorld()->isUnderwater(caster.getCell(), pos)) // Underwater casting not possible return; @@ -237,7 +243,8 @@ namespace MWWorld if (hit) { - MWBase::Environment::get().getWorld()->explodeSpell(pos, it->mEffects, caster, ESM::RT_Target, it->mSpellId, it->mSourceName); + MWBase::Environment::get().getWorld()->explodeSpell(pos, it->mEffects, caster, result.mHitObject, + ESM::RT_Target, it->mSpellId, it->mSourceName); MWBase::Environment::get().getSoundManager()->stopSound(it->mSound); mParent->removeChild(it->mNode); diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index 1da6b53fa4..f85abcc32b 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -8,8 +8,18 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +namespace +{ +enum RefDataFlags +{ + Flag_SuppressActivate = 1, // If set, activation will be suppressed and redirected to the OnActivate flag, which can then be handled by a script. + Flag_OnActivate = 2 +}; +} + namespace MWWorld { + void RefData::copy (const RefData& refData) { mBaseNode = refData.mBaseNode; @@ -19,6 +29,7 @@ namespace MWWorld mPosition = refData.mPosition; mChanged = refData.mChanged; mDeletedByContentFile = refData.mDeletedByContentFile; + mFlags = refData.mFlags; mCustomData = refData.mCustomData ? refData.mCustomData->clone() : 0; } @@ -32,7 +43,7 @@ namespace MWWorld } RefData::RefData() - : mBaseNode(0), mDeletedByContentFile(false), mEnabled (true), mCount (1), mCustomData (0), mChanged(false) + : mBaseNode(0), mDeletedByContentFile(false), mEnabled (true), mCount (1), mCustomData (0), mChanged(false), mFlags(0) { for (int i=0; i<3; ++i) { @@ -45,7 +56,7 @@ namespace MWWorld : mBaseNode(0), mDeletedByContentFile(false), mEnabled (true), mCount (1), mPosition (cellRef.mPos), mCustomData (0), - mChanged(false) // Loading from ESM/ESP files -> assume unchanged + mChanged(false), mFlags(0) // Loading from ESM/ESP files -> assume unchanged { } @@ -55,8 +66,12 @@ namespace MWWorld mCount (objectState.mCount), mPosition (objectState.mPosition), mCustomData (0), - mChanged(true) // Loading from a savegame -> assume changed + mChanged(true), mFlags(objectState.mFlags) // Loading from a savegame -> assume changed { + // "Note that the ActivationFlag_UseEnabled is saved to the reference, + // which will result in permanently suppressed activation if the reference script is removed. + // This occurred when removing the animated containers mod, and the fix in MCP is to reset UseEnabled to true on loading a game." + mFlags &= (~Flag_SuppressActivate); } RefData::RefData (const RefData& refData) @@ -80,6 +95,7 @@ namespace MWWorld objectState.mEnabled = mEnabled; objectState.mCount = mCount; objectState.mPosition = mPosition; + objectState.mFlags = mFlags; } RefData& RefData::operator= (const RefData& refData) @@ -219,4 +235,38 @@ namespace MWWorld { return mChanged; } + + bool RefData::activate() + { + if (!(mFlags & Flag_SuppressActivate)) + return true; + else + { + mFlags |= Flag_OnActivate; + return false; + } + } + + bool RefData::onActivate() + { + mFlags |= Flag_SuppressActivate; + + if (mFlags & Flag_OnActivate) + { + mFlags &= (~Flag_OnActivate); + return true; + } + return false; + } + + bool RefData::activateByScript() + { + if (mFlags & Flag_SuppressActivate) + { + mFlags &= (~Flag_SuppressActivate); + return true; + } + else + return false; + } } diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index 28d2dad4c9..9e662e430c 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -5,6 +5,7 @@ #include "../mwscript/locals.hpp" +#include #include namespace SceneUtil @@ -49,6 +50,8 @@ namespace MWWorld bool mChanged; + unsigned int mFlags; + public: RefData(); @@ -121,6 +124,12 @@ namespace MWWorld const CustomData *getCustomData() const; + bool activate(); + + bool onActivate(); + + bool activateByScript(); + bool hasChanged() const; ///< Has this RefData changed since it was originally loaded? }; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 15b3c057bc..7061e4418f 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -24,6 +24,7 @@ #include "class.hpp" #include "cellvisitors.hpp" #include "cellstore.hpp" +#include "cellpreloader.hpp" namespace { @@ -86,10 +87,13 @@ namespace MWPhysics::PhysicsSystem& mPhysics; MWRender::RenderingManager& mRendering; + std::vector mToInsert; + InsertVisitor (MWWorld::CellStore& cell, bool rescale, Loading::Listener& loadingListener, MWPhysics::PhysicsSystem& physics, MWRender::RenderingManager& rendering); bool operator() (const MWWorld::Ptr& ptr); + void insert(); }; InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, bool rescale, @@ -102,33 +106,43 @@ namespace bool InsertVisitor::operator() (const MWWorld::Ptr& ptr) { - if (mRescale) - { - if (ptr.getCellRef().getScale()<0.5) - ptr.getCellRef().setScale(0.5); - else if (ptr.getCellRef().getScale()>2) - ptr.getCellRef().setScale(2); - } - - if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled()) - { - try - { - addObject(ptr, mPhysics, mRendering); - updateObjectRotation(ptr, mPhysics, mRendering, false); - } - catch (const std::exception& e) - { - std::string error ("error during rendering '" + ptr.getCellRef().getRefId() + "': "); - std::cerr << error + e.what() << std::endl; - } - } - - mLoadingListener.increaseProgress (1); - + // do not insert directly as we can't modify the cell from within the visitation + // CreatureLevList::insertObjectRendering may spawn a new creature + mToInsert.push_back(ptr); return true; } + void InsertVisitor::insert() + { + for (std::vector::iterator it = mToInsert.begin(); it != mToInsert.end(); ++it) + { + MWWorld::Ptr ptr = *it; + if (mRescale) + { + if (ptr.getCellRef().getScale()<0.5) + ptr.getCellRef().setScale(0.5); + else if (ptr.getCellRef().getScale()>2) + ptr.getCellRef().setScale(2); + } + + if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled()) + { + try + { + addObject(ptr, mPhysics, mRendering); + updateObjectRotation(ptr, mPhysics, mRendering, false); + } + catch (const std::exception& e) + { + std::string error ("error during rendering '" + ptr.getCellRef().getRefId() + "': "); + std::cerr << error + e.what() << std::endl; + } + } + + mLoadingListener.increaseProgress (1); + } + } + struct AdjustPositionVisitor { bool operator() (const MWWorld::Ptr& ptr) @@ -179,28 +193,19 @@ namespace MWWorld void Scene::update (float duration, bool paused) { - if (mNeedMapUpdate) + if (mPreloadEnabled) { - // Note: exterior cell maps must be updated, even if they were visited before, because the set of surrounding cells might be different - // (and objects in a different cell can "bleed" into another cells map if they cross the border) - std::set cellsToUpdate; - for (CellStoreCollection::iterator active = mActiveCells.begin(); active!=mActiveCells.end(); ++active) + mPreloadTimer += duration; + if (mPreloadTimer > 0.25f) { - cellsToUpdate.insert(*active); - } - MWBase::Environment::get().getWindowManager()->requestMap(cellsToUpdate); - - mNeedMapUpdate = false; - - if (mCurrentCell->isExterior()) - { - int cellX, cellY; - getGridCenter(cellX, cellY); - MWBase::Environment::get().getWindowManager()->setActiveMap(cellX,cellY,false); + preloadCells(); + mPreloadTimer = 0.f; } } mRendering.update (duration, paused); + + mPreloader->updateCache(mRendering.getReferenceTime()); } void Scene::unloadCell (CellStoreCollection::iterator iter) @@ -237,7 +242,7 @@ namespace MWWorld mActiveCells.erase(*iter); } - void Scene::loadCell (CellStore *cell, Loading::Listener* loadingListener) + void Scene::loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn) { std::pair result = mActiveCells.insert(cell); @@ -265,9 +270,21 @@ namespace MWWorld mPhysics->addHeightField (data->mHeights, cell->getCell()->getGridX(), cell->getCell()->getGridY(), worldsize / (verts-1), verts); } + else + { + static std::vector defaultHeight; + defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT); + mPhysics->addHeightField (&defaultHeight[0], cell->getCell()->getGridX(), cell->getCell()->getGridY(), + worldsize / (verts-1), verts); + } } - cell->respawn(); + // register local scripts + // do this before insertCell, to make sure we don't add scripts from levelled creature spawning twice + MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell); + + if (respawn) + cell->respawn(); // ... then references. This is important for adjustPosition to work correctly. /// \todo rescale depending on the state of a new GMST @@ -289,9 +306,7 @@ namespace MWWorld mRendering.configureAmbient(cell->getCell()); } - // register local scripts - // ??? Should this go into the above if block ??? - MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell); + mPreloader->notifyLoaded(cell); } void Scene::changeToVoid() @@ -313,7 +328,7 @@ namespace MWWorld getGridCenter(cellX, cellY); float centerX, centerY; MWBase::Environment::get().getWorld()->indexToPosition(cellX, cellY, centerX, centerY, true); - const float maxDistance = 8192/2 + 1024; // 1/2 cell size + threshold + const float maxDistance = 8192/2 + mCellLoadingThreshold; // 1/2 cell size + threshold float distance = std::max(std::abs(centerX-pos.x()), std::abs(centerY-pos.y())); if (distance > maxDistance) { @@ -324,7 +339,7 @@ namespace MWWorld } } - void Scene::changeCellGrid (int X, int Y) + void Scene::changeCellGrid (int X, int Y, bool changeEvent) { Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); @@ -334,15 +349,13 @@ namespace MWWorld std::string loadingExteriorText = "#{sLoadingMessage3}"; loadingListener->setLabel(loadingExteriorText); - const int halfGridSize = Settings::Manager::getInt("exterior cell load distance", "Cells"); - CellStoreCollection::iterator active = mActiveCells.begin(); while (active!=mActiveCells.end()) { if ((*active)->getCell()->isExterior()) { - if (std::abs (X-(*active)->getCell()->getGridX())<=halfGridSize && - std::abs (Y-(*active)->getCell()->getGridY())<=halfGridSize) + if (std::abs (X-(*active)->getCell()->getGridX())<=mHalfGridSize && + std::abs (Y-(*active)->getCell()->getGridY())<=mHalfGridSize) { // keep cells within the new grid ++active; @@ -354,9 +367,9 @@ namespace MWWorld int refsToLoad = 0; // get the number of refs to load - for (int x=X-halfGridSize; x<=X+halfGridSize; ++x) + for (int x=X-mHalfGridSize; x<=X+mHalfGridSize; ++x) { - for (int y=Y-halfGridSize; y<=Y+halfGridSize; ++y) + for (int y=Y-mHalfGridSize; y<=Y+mHalfGridSize; ++y) { CellStoreCollection::iterator iter = mActiveCells.begin(); @@ -379,9 +392,9 @@ namespace MWWorld loadingListener->setProgressRange(refsToLoad); // Load cells - for (int x=X-halfGridSize; x<=X+halfGridSize; ++x) + for (int x=X-mHalfGridSize; x<=X+mHalfGridSize; ++x) { - for (int y=Y-halfGridSize; y<=Y+halfGridSize; ++y) + for (int y=Y-mHalfGridSize; y<=Y+mHalfGridSize; ++y) { CellStoreCollection::iterator iter = mActiveCells.begin(); @@ -400,7 +413,7 @@ namespace MWWorld { CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y); - loadCell (cell, loadingListener); + loadCell (cell, loadingListener, changeEvent); } } } @@ -408,13 +421,8 @@ namespace MWWorld CellStore* current = MWBase::Environment::get().getWorld()->getExterior(X,Y); MWBase::Environment::get().getWindowManager()->changeCell(current); - mCellChanged = true; - - // Delay the map update until scripts have been given a chance to run. - // If we don't do this, objects that should be disabled will still appear on the map. - mNeedMapUpdate = true; - - mRendering.getResourceSystem()->clearCache(); + if (changeEvent) + mCellChanged = true; } void Scene::changePlayerCell(CellStore *cell, const ESM::Position &pos, bool adjustPlayerPos) @@ -436,7 +444,8 @@ namespace MWWorld float z = pos.rot[2]; world->rotateObject(player, x, y, z); - player.getClass().adjustPosition(player, true); + if (adjustPlayerPos) + player.getClass().adjustPosition(player, true); } MWBase::MechanicsManager *mechMgr = @@ -449,8 +458,27 @@ namespace MWWorld } Scene::Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics) - : mCurrentCell (0), mCellChanged (false), mPhysics(physics), mRendering(rendering), mNeedMapUpdate(false) + : mCurrentCell (0), mCellChanged (false), mPhysics(physics), mRendering(rendering) + , mPreloadTimer(0.f) + , mHalfGridSize(Settings::Manager::getInt("exterior cell load distance", "Cells")) + , mCellLoadingThreshold(1024.f) + , mPreloadDistance(Settings::Manager::getInt("preload distance", "Cells")) + , mPreloadEnabled(Settings::Manager::getBool("preload enabled", "Cells")) + , mPreloadExteriorGrid(Settings::Manager::getBool("preload exterior grid", "Cells")) + , mPreloadDoors(Settings::Manager::getBool("preload doors", "Cells")) + , mPreloadFastTravel(Settings::Manager::getBool("preload fast travel", "Cells")) { + mPreloader.reset(new CellPreloader(rendering.getResourceSystem(), physics->getShapeManager(), rendering.getTerrain())); + mPreloader->setWorkQueue(mRendering.getWorkQueue()); + + mPhysics->setUnrefQueue(rendering.getUnrefQueue()); + + rendering.getResourceSystem()->setExpiryDelay(Settings::Manager::getFloat("cache expiry delay", "Cells")); + + mPreloader->setExpiryDelay(Settings::Manager::getFloat("preload cell expiry delay", "Cells")); + mPreloader->setMinCacheSize(Settings::Manager::getInt("preload cell cache min", "Cells")); + mPreloader->setMaxCacheSize(Settings::Manager::getInt("preload cell cache max", "Cells")); + mPreloader->setPreloadInstances(Settings::Manager::getBool("preload instances", "Cells")); } Scene::~Scene() @@ -467,7 +495,7 @@ namespace MWWorld return mActiveCells; } - void Scene::changeToInteriorCell (const std::string& cellName, const ESM::Position& position) + void Scene::changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent) { CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(cellName); bool loadcell = (mCurrentCell == NULL); @@ -493,7 +521,8 @@ namespace MWWorld float z = position.rot[2]; world->rotateObject(world->getPlayerPtr(), x, y, z); - world->getPlayerPtr().getClass().adjustPosition(world->getPlayerPtr(), true); + if (adjustPlayerPos) + world->getPlayerPtr().getClass().adjustPosition(world->getPlayerPtr(), true); MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5); return; } @@ -513,9 +542,9 @@ namespace MWWorld loadingListener->setProgressRange(refsToLoad); // Load cell. - loadCell (cell, loadingListener); + loadCell (cell, loadingListener, changeEvent); - changePlayerCell(cell, position, true); + changePlayerCell(cell, position, adjustPlayerPos); // adjust fog mRendering.configureFog(mCurrentCell->getCell()); @@ -523,25 +552,22 @@ namespace MWWorld // Sky system MWBase::Environment::get().getWorld()->adjustSky(); - mCellChanged = true; MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5); + if (changeEvent) + mCellChanged = true; + + MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5); MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell); - - // Delay the map update until scripts have been given a chance to run. - // If we don't do this, objects that should be disabled will still appear on the map. - mNeedMapUpdate = true; - - mRendering.getResourceSystem()->clearCache(); } - void Scene::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos) + void Scene::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent) { int x = 0; int y = 0; MWBase::Environment::get().getWorld()->positionToIndex (position.pos[0], position.pos[1], x, y); - changeCellGrid(x, y); + changeCellGrid(x, y, changeEvent); CellStore* current = MWBase::Environment::get().getWorld()->getExterior(x, y); changePlayerCell(current, position, adjustPlayerPos); @@ -563,6 +589,7 @@ namespace MWWorld { InsertVisitor insertVisitor (cell, rescale, *loadingListener, *mPhysics, mRendering); cell.forEach (insertVisitor); + insertVisitor.insert(); // do adjustPosition (snapping actors to ground) after objects are loaded, so we don't depend on the loading order AdjustPositionVisitor adjustPosVisitor; @@ -614,4 +641,170 @@ namespace MWWorld return Ptr(); } + + void Scene::preloadCells() + { + if (mPreloadDoors) + preloadTeleportDoorDestinations(); + if (mPreloadExteriorGrid) + preloadExteriorGrid(); + if (mPreloadFastTravel) + preloadFastTravelDestinations(); + } + + void Scene::preloadTeleportDoorDestinations() + { + std::vector teleportDoors; + for (CellStoreCollection::const_iterator iter (mActiveCells.begin()); + iter!=mActiveCells.end(); ++iter) + { + const MWWorld::CellStore* cellStore = *iter; + typedef MWWorld::CellRefList::List DoorList; + const DoorList &doors = cellStore->getReadOnlyDoors().mList; + for (DoorList::const_iterator doorIt = doors.begin(); doorIt != doors.end(); ++doorIt) + { + if (!doorIt->mRef.getTeleport()) { + continue; + } + teleportDoors.push_back(MWWorld::ConstPtr(&*doorIt, cellStore)); + } + } + + const MWWorld::ConstPtr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + for (std::vector::iterator it = teleportDoors.begin(); it != teleportDoors.end(); ++it) + { + const MWWorld::ConstPtr& door = *it; + float sqrDistToPlayer = (player.getRefData().getPosition().asVec3() - door.getRefData().getPosition().asVec3()).length2(); + + if (sqrDistToPlayer < mPreloadDistance*mPreloadDistance) + { + try + { + if (!door.getCellRef().getDestCell().empty()) + preloadCell(MWBase::Environment::get().getWorld()->getInterior(door.getCellRef().getDestCell())); + else + { + int x,y; + MWBase::Environment::get().getWorld()->positionToIndex (door.getCellRef().getDoorDest().pos[0], door.getCellRef().getDoorDest().pos[1], x, y); + preloadCell(MWBase::Environment::get().getWorld()->getExterior(x,y), true); + } + } + catch (std::exception& e) + { + // ignore error for now, would spam the log too much + } + } + } + } + + void Scene::preloadExteriorGrid() + { + if (!MWBase::Environment::get().getWorld()->isCellExterior()) + return; + + int halfGridSizePlusOne = mHalfGridSize + 1; + + const MWWorld::ConstPtr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + osg::Vec3f playerPos = player.getRefData().getPosition().asVec3(); + + int cellX,cellY; + getGridCenter(cellX,cellY); + + float centerX, centerY; + MWBase::Environment::get().getWorld()->indexToPosition(cellX, cellY, centerX, centerY, true); + + for (int dx = -halfGridSizePlusOne; dx <= halfGridSizePlusOne; ++dx) + { + for (int dy = -halfGridSizePlusOne; dy <= halfGridSizePlusOne; ++dy) + { + if (dy != halfGridSizePlusOne && dy != -halfGridSizePlusOne && dx != halfGridSizePlusOne && dx != -halfGridSizePlusOne) + continue; // only care about the outer (not yet loaded) part of the grid + + float thisCellCenterX, thisCellCenterY; + MWBase::Environment::get().getWorld()->indexToPosition(cellX+dx, cellY+dy, thisCellCenterX, thisCellCenterY, true); + + float dist = std::max(std::abs(thisCellCenterX - playerPos.x()), std::abs(thisCellCenterY - playerPos.y())); + float loadDist = 8192/2 + 8192 - mCellLoadingThreshold + mPreloadDistance; + + if (dist < loadDist) + preloadCell(MWBase::Environment::get().getWorld()->getExterior(cellX+dx, cellY+dy)); + } + } + } + + void Scene::preloadCell(CellStore *cell, bool preloadSurrounding) + { + if (preloadSurrounding && cell->isExterior()) + { + int x = cell->getCell()->getGridX(); + int y = cell->getCell()->getGridY(); + unsigned int numpreloaded = 0; + for (int dx = -mHalfGridSize; dx <= mHalfGridSize; ++dx) + { + for (int dy = -mHalfGridSize; dy <= mHalfGridSize; ++dy) + { + mPreloader->preload(MWBase::Environment::get().getWorld()->getExterior(x+dx, y+dy), mRendering.getReferenceTime()); + if (++numpreloaded >= mPreloader->getMaxCacheSize()) + break; + } + } + } + else + mPreloader->preload(cell, mRendering.getReferenceTime()); + } + + struct ListFastTravelDestinationsVisitor + { + ListFastTravelDestinationsVisitor(float preloadDist, const osg::Vec3f& playerPos) + : mPreloadDist(preloadDist) + , mPlayerPos(playerPos) + { + } + + bool operator()(const MWWorld::Ptr& ptr) + { + if ((ptr.getRefData().getPosition().asVec3() - mPlayerPos).length2() > mPreloadDist * mPreloadDist) + return true; + + if (ptr.getClass().isNpc()) + { + const std::vector& transport = ptr.get()->mBase->mTransport.mList; + mList.insert(mList.begin(), transport.begin(), transport.end()); + } + else + { + const std::vector& transport = ptr.get()->mBase->mTransport.mList; + mList.insert(mList.begin(), transport.begin(), transport.end()); + } + return true; + } + float mPreloadDist; + osg::Vec3f mPlayerPos; + std::vector mList; + }; + + void Scene::preloadFastTravelDestinations() + { + const MWWorld::ConstPtr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + ListFastTravelDestinationsVisitor listVisitor(mPreloadDistance, player.getRefData().getPosition().asVec3()); + + for (CellStoreCollection::const_iterator iter (mActiveCells.begin()); iter!=mActiveCells.end(); ++iter) + { + MWWorld::CellStore* cellStore = *iter; + cellStore->forEachType(listVisitor); + cellStore->forEachType(listVisitor); + } + + for (std::vector::const_iterator it = listVisitor.mList.begin(); it != listVisitor.mList.end(); ++it) + { + if (!it->mCellName.empty()) + preloadCell(MWBase::Environment::get().getWorld()->getInterior(it->mCellName)); + else + { + int x,y; + MWBase::Environment::get().getWorld()->positionToIndex( it->mPos.pos[0], it->mPos.pos[1], x, y); + preloadCell(MWBase::Environment::get().getWorld()->getExterior(x,y), true); + } + } + } } diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 439c8d72c3..33886eed46 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -1,12 +1,11 @@ #ifndef GAME_MWWORLD_SCENE_H #define GAME_MWWORLD_SCENE_H -//#include "../mwrender/renderingmanager.hpp" - #include "ptr.hpp" #include "globals.hpp" #include +#include namespace osg { @@ -43,6 +42,7 @@ namespace MWWorld { class Player; class CellStore; + class CellPreloader; class Scene { @@ -57,16 +57,31 @@ namespace MWWorld bool mCellChanged; MWPhysics::PhysicsSystem *mPhysics; MWRender::RenderingManager& mRendering; + std::auto_ptr mPreloader; + float mPreloadTimer; + int mHalfGridSize; + float mCellLoadingThreshold; + float mPreloadDistance; + bool mPreloadEnabled; - bool mNeedMapUpdate; + bool mPreloadExteriorGrid; + bool mPreloadDoors; + bool mPreloadFastTravel; void insertCell (CellStore &cell, bool rescale, Loading::Listener* loadingListener); // Load and unload cells as necessary to create a cell grid with "X" and "Y" in the center - void changeCellGrid (int X, int Y); + void changeCellGrid (int X, int Y, bool changeEvent = true); void getGridCenter(int& cellX, int& cellY); + void preloadCells(); + void preloadTeleportDoorDestinations(); + void preloadExteriorGrid(); + void preloadFastTravelDestinations(); + + void preloadCell(MWWorld::CellStore* cell, bool preloadSurrounding=false); + public: Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics); @@ -75,7 +90,7 @@ namespace MWWorld void unloadCell (CellStoreCollection::iterator iter); - void loadCell (CellStore *cell, Loading::Listener* loadingListener); + void loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn); void playerMoved (const osg::Vec3f& pos); @@ -88,11 +103,13 @@ namespace MWWorld bool hasCellChanged() const; ///< Has the set of active cells changed, since the last frame? - void changeToInteriorCell (const std::string& cellName, const ESM::Position& position); + void changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent=true); ///< Move to interior cell. + /// @param changeEvent Set cellChanged flag? - void changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos); + void changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent=true); ///< Move to exterior cell. + /// @param changeEvent Set cellChanged flag? void changeToVoid(); ///< Change into a void diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index d1ba0ef0b5..4cddbcdfb1 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -8,6 +8,7 @@ #include #include +#include namespace { @@ -512,12 +513,13 @@ namespace MWWorld bool deleted = false; cell->getNextRef(esm, ref, deleted); - // Add data required to make reference appear in the correct cell. - // We should not need to test for duplicates, as this part of the code is pre-cell merge. - cell->mMovedRefs.push_back(cMRef); - // But there may be duplicates here! if (!deleted) { + // Add data required to make reference appear in the correct cell. + // We should not need to test for duplicates, as this part of the code is pre-cell merge. + cell->mMovedRefs.push_back(cMRef); + + // But there may be duplicates here! ESM::CellRefTracker::iterator iter = std::find(cellAlt->mLeasedRefs.begin(), cellAlt->mLeasedRefs.end(), ref.mRefNum); if (iter == cellAlt->mLeasedRefs.end()) cellAlt->mLeasedRefs.push_back(ref); @@ -680,7 +682,10 @@ namespace MWWorld ESM::MovedCellRef target0 = *itold; ESM::Cell *wipecell = const_cast(search(target0.mTarget[0], target0.mTarget[1])); ESM::CellRefTracker::iterator it_lease = std::find(wipecell->mLeasedRefs.begin(), wipecell->mLeasedRefs.end(), it->mRefNum); - wipecell->mLeasedRefs.erase(it_lease); + if (it_lease != wipecell->mLeasedRefs.end()) + wipecell->mLeasedRefs.erase(it_lease); + else + std::cerr << "can't find " << it->mRefNum.mIndex << " " << it->mRefNum.mContentFile << " in leasedRefs " << std::endl; *itold = *it; } else diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 5ce3d5c2fe..1cfba0daf1 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -20,7 +21,6 @@ #include "player.hpp" #include "esmstore.hpp" -#include "fallback.hpp" #include "cellstore.hpp" #include @@ -100,7 +100,7 @@ template class TimeOfDayInterpolator; template class TimeOfDayInterpolator; Weather::Weather(const std::string& name, - const MWWorld::Fallback& fallback, + const Fallback::Map& fallback, float stormWindSpeed, float rainSpeed, const std::string& particleEffect) @@ -321,13 +321,17 @@ void RegionWeather::chooseNewWeather() { sum += mChances[i]; if(chance <= sum) - break; + { + mWeather = i; + return; + } } - mWeather = i; + // if we hit this path then the chances don't add to 100, choose a default weather instead + mWeather = 0; } -MoonModel::MoonModel(const std::string& name, const MWWorld::Fallback& fallback) +MoonModel::MoonModel(const std::string& name, const Fallback::Map& fallback) : mFadeInStart(fallback.getFallbackFloat("Moons_" + name + "_Fade_In_Start")) , mFadeInFinish(fallback.getFallbackFloat("Moons_" + name + "_Fade_In_Finish")) , mFadeOutStart(fallback.getFallbackFloat("Moons_" + name + "_Fade_Out_Start")) @@ -496,7 +500,7 @@ inline float MoonModel::earlyMoonShadowAlpha(float angle) const return 0.0f; } -WeatherManager::WeatherManager(MWRender::RenderingManager& rendering, const MWWorld::Fallback& fallback, MWWorld::ESMStore& store) +WeatherManager::WeatherManager(MWRender::RenderingManager& rendering, const Fallback::Map& fallback, MWWorld::ESMStore& store) : mStore(store) , mRendering(rendering) , mSunriseTime(fallback.getFallbackFloat("Weather_Sunrise_Time")) @@ -718,7 +722,7 @@ void WeatherManager::update(float duration, bool paused) mRendering.configureFog(mResult.mFogDepth, underwaterFog, mResult.mFogColor); mRendering.setAmbientColour(mResult.mAmbientColor); - mRendering.setSunColour(mResult.mSunColor); + mRendering.setSunColour(mResult.mSunColor, mResult.mSunColor * mResult.mGlareView); mRendering.getSkyManager()->setWeather(mResult); @@ -862,7 +866,7 @@ void WeatherManager::clear() } inline void WeatherManager::addWeather(const std::string& name, - const MWWorld::Fallback& fallback, + const Fallback::Map& fallback, const std::string& particleEffect) { static const float fStromWindSpeed = mStore.get().find("fStromWindSpeed")->getFloat(); @@ -888,8 +892,7 @@ inline void WeatherManager::regionalWeatherChanged(const std::string& regionID, MWWorld::ConstPtr player = MWMechanics::getPlayer(); if(player.isInCell()) { - std::string playerRegion = Misc::StringUtils::lowerCase(player.getCell()->getCell()->mRegion); - if(!playerRegion.empty() && (playerRegion == regionID)) + if(Misc::StringUtils::ciEqual(regionID, mCurrentRegion)) { addWeatherTransition(region.getWeather()); } diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index a5627a507c..0442007570 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -29,9 +29,13 @@ namespace Loading class Listener; } +namespace Fallback +{ + class Map; +} + namespace MWWorld { - class Fallback; class TimeStamp; @@ -66,7 +70,7 @@ namespace MWWorld { public: Weather(const std::string& name, - const MWWorld::Fallback& fallback, + const Fallback::Map& fallback, float stormWindSpeed, float rainSpeed, const std::string& particleEffect); @@ -172,7 +176,7 @@ namespace MWWorld class MoonModel { public: - MoonModel(const std::string& name, const MWWorld::Fallback& fallback); + MoonModel(const std::string& name, const Fallback::Map& fallback); MWRender::MoonState calculateState(const TimeStamp& gameTime) const; @@ -203,7 +207,7 @@ namespace MWWorld public: // Have to pass fallback and Store, can't use singleton since World isn't fully constructed yet at the time WeatherManager(MWRender::RenderingManager& rendering, - const MWWorld::Fallback& fallback, + const Fallback::Map& fallback, MWWorld::ESMStore& store); ~WeatherManager(); @@ -288,7 +292,7 @@ namespace MWWorld std::string mPlayingSoundID; void addWeather(const std::string& name, - const MWWorld::Fallback& fallback, + const Fallback::Map& fallback, const std::string& particleEffect = ""); void importRegions(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 15826009d6..1706f7681a 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1,13 +1,5 @@ #include "worldimp.hpp" -#if defined(_WIN32) && !defined(__MINGW32__) -#include -#elif defined HAVE_UNORDERED_MAP -#include -#else -#include -#endif - #include #include @@ -108,7 +100,7 @@ namespace MWWorld } private: - typedef std::tr1::unordered_map LoadersContainer; + typedef std::map LoadersContainer; LoadersContainer mLoaders; }; @@ -247,13 +239,13 @@ namespace MWWorld if (findExteriorPosition (mStartCell, pos)) { - changeToExteriorCell (pos); + changeToExteriorCell (pos, true); fixPosition(getPlayerPtr()); } else { findInteriorPosition (mStartCell, pos); - changeToInteriorCell (mStartCell, pos); + changeToInteriorCell (mStartCell, pos, true); } } else @@ -433,7 +425,7 @@ namespace MWWorld // Werewolf (BM) gmst["fWereWolfRunMult"] = ESM::Variant(1.f); gmst["fWereWolfSilverWeaponDamageMult"] = ESM::Variant(1.f); - + gmst["iWerewolfFightMod"] = ESM::Variant(1); std::map globals; // vanilla Morrowind does not define dayspassed. @@ -511,7 +503,7 @@ namespace MWWorld return 0; } - const MWWorld::Fallback *World::getFallback() const + const Fallback::Map *World::getFallback() const { return &mFallback; } @@ -812,6 +804,9 @@ namespace MWWorld mWeatherManager->advanceTime (hours, incremental); + if (!incremental) + mProjectileManager->clear(); + hours += mGameHour->getFloat(); setHour (hours); @@ -958,11 +953,11 @@ namespace MWWorld return mTimeScale->getFloat(); } - void World::changeToInteriorCell (const std::string& cellName, const ESM::Position& position) + void World::changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent) { mPhysics->clearQueuedMovement(); - if (mCurrentWorldSpace != cellName) + if (changeEvent && mCurrentWorldSpace != cellName) { // changed worldspace mProjectileManager->clear(); @@ -972,34 +967,34 @@ namespace MWWorld } removeContainerScripts(getPlayerPtr()); - mWorldScene->changeToInteriorCell(cellName, position); + mWorldScene->changeToInteriorCell(cellName, position, adjustPlayerPos, changeEvent); addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell()); } - void World::changeToExteriorCell (const ESM::Position& position) + void World::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent) { mPhysics->clearQueuedMovement(); - if (mCurrentWorldSpace != "sys::default") // FIXME + if (changeEvent && mCurrentWorldSpace != ESM::CellId::sDefaultWorldspace) { // changed worldspace mProjectileManager->clear(); mRendering->notifyWorldSpaceChanged(); } removeContainerScripts(getPlayerPtr()); - mWorldScene->changeToExteriorCell(position, true); + mWorldScene->changeToExteriorCell(position, adjustPlayerPos, changeEvent); addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell()); } - void World::changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool detectWorldSpaceChange) + void World::changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent) { - if (!detectWorldSpaceChange) + if (!changeEvent) mCurrentWorldSpace = cellId.mWorldspace; if (cellId.mPaged) - changeToExteriorCell (position); + changeToExteriorCell (position, adjustPlayerPos, changeEvent); else - changeToInteriorCell (cellId.mWorldspace, position); + changeToInteriorCell (cellId.mWorldspace, position, adjustPlayerPos, changeEvent); } void World::markCellAsUnchanged() @@ -1013,7 +1008,7 @@ namespace MWWorld return static_cast(mActivationDistanceOverride); static const int iMaxActivateDist = getStore().get().find("iMaxActivateDist")->getInt(); - return iMaxActivateDist * 5.f / 4.f; + return static_cast(iMaxActivateDist); } MWWorld::Ptr World::getFacedObject() @@ -1047,21 +1042,33 @@ namespace MWWorld if(!node) node = anim->getNode("Bip01 Head"); if(node) { - osg::MatrixList mats = node->getWorldMatrices(); - if(!mats.empty()) - return mats[0]; + osg::NodePathList nodepaths = node->getParentalNodePaths(); + if(!nodepaths.empty()) + return osg::computeLocalToWorld(nodepaths[0]); } } return osg::Matrixf::translate(actor.getRefData().getPosition().asVec3()); } - std::pair World::getHitContact(const MWWorld::ConstPtr &ptr, float distance) { const ESM::Position &posdata = ptr.getRefData().getPosition(); osg::Quat rot = osg::Quat(posdata.rot[0], osg::Vec3f(-1,0,0)) * osg::Quat(posdata.rot[2], osg::Vec3f(0,0,-1)); - osg::Vec3f pos = getActorHeadTransform(ptr).getTrans(); + + osg::Vec3f pos = ptr.getRefData().getPosition().asVec3(); + + if (ptr == getPlayerPtr()) + pos = getActorHeadTransform(ptr).getTrans(); // special cased for better aiming with the camera + else + { + // general case, compatible with all types of different creatures + // note: we intentionally do *not* use the collision box offset here, this is required to make + // some flying creatures work that have their collision box offset in the air + osg::Vec3f halfExtents = mPhysics->getHalfExtents(ptr); + pos.z() += halfExtents.z() * 2 * 0.75; + distance += halfExtents.y(); + } std::pair result = mPhysics->getHitContact(ptr, pos, rot, distance); if(result.first.isEmpty()) @@ -1109,7 +1116,7 @@ namespace MWWorld } } - MWWorld::Ptr World::moveObject(const Ptr &ptr, CellStore* newCell, float x, float y, float z) + MWWorld::Ptr World::moveObject(const Ptr &ptr, CellStore* newCell, float x, float y, float z, bool movePhysics) { ESM::Position pos = ptr.getRefData().getPosition(); @@ -1133,7 +1140,7 @@ namespace MWWorld if (isPlayer) { if (!newCell->isExterior()) - changeToInteriorCell(Misc::StringUtils::lowerCase(newCell->getCell()->mName), pos); + changeToInteriorCell(Misc::StringUtils::lowerCase(newCell->getCell()->mName), pos, false); else { if (mWorldScene->isCellActive(*newCell)) @@ -1197,7 +1204,8 @@ namespace MWWorld if (haveToMove && newPtr.getRefData().getBaseNode()) { mRendering->moveObject(newPtr, vec); - mPhysics->updatePosition(newPtr); + if (movePhysics) + mPhysics->updatePosition(newPtr); } if (isPlayer) { @@ -1206,7 +1214,7 @@ namespace MWWorld return newPtr; } - MWWorld::Ptr World::moveObjectImp(const Ptr& ptr, float x, float y, float z) + MWWorld::Ptr World::moveObjectImp(const Ptr& ptr, float x, float y, float z, bool movePhysics) { CellStore *cell = ptr.getCell(); @@ -1217,7 +1225,7 @@ namespace MWWorld cell = getExterior(cellX, cellY); } - return moveObject(ptr, cell, x, y, z); + return moveObject(ptr, cell, x, y, z, movePhysics); } MWWorld::Ptr World::moveObject (const Ptr& ptr, float x, float y, float z) @@ -1311,7 +1319,8 @@ namespace MWWorld actor.getRefData().setPosition(pos); osg::Vec3f traced = mPhysics->traceDown(actor, dist*1.1f); - moveObject(actor, actor.getCell(), traced.x(), traced.y(), traced.z()); + if (traced != pos.asVec3()) + moveObject(actor, actor.getCell(), traced.x(), traced.y(), traced.z()); } void World::rotateObject (const Ptr& ptr,float x,float y,float z, bool adjust) @@ -1319,11 +1328,63 @@ namespace MWWorld rotateObjectImp(ptr, osg::Vec3f(x, y, z), adjust); } - MWWorld::Ptr World::safePlaceObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos) + MWWorld::Ptr World::placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos) { return copyObjectToCell(ptr,cell,pos,ptr.getRefData().getCount(),false); } + MWWorld::Ptr World::safePlaceObject(const ConstPtr &ptr, const ConstPtr &referenceObject, MWWorld::CellStore* referenceCell, int direction, float distance) + { + ESM::Position ipos = referenceObject.getRefData().getPosition(); + osg::Vec3f pos(ipos.asVec3()); + osg::Quat orientation(ipos.rot[2], osg::Vec3f(0,0,-1)); + + int fallbackDirections[4] = {direction, (direction+3)%4, (direction+2)%4, (direction+1)%4}; + + osg::Vec3f spawnPoint = pos; + + for (int i=0; i<4; ++i) + { + direction = fallbackDirections[i]; + if (direction == 0) spawnPoint = pos + (orientation * osg::Vec3f(0,1,0)) * distance; + else if(direction == 1) spawnPoint = pos - (orientation * osg::Vec3f(0,1,0)) * distance; + else if(direction == 2) spawnPoint = pos - (orientation * osg::Vec3f(1,0,0)) * distance; + else if(direction == 3) spawnPoint = pos + (orientation * osg::Vec3f(1,0,0)) * distance; + + if (!ptr.getClass().isActor()) + break; + + // check if spawn point is safe, fall back to another direction if not + spawnPoint.z() += 30; // move up a little to account for slopes, will snap down later + + if (!castRay(spawnPoint.x(), spawnPoint.y(), spawnPoint.z(), + pos.x(), pos.y(), pos.z() + 20)) + { + // safe + break; + } + } + ipos.pos[0] = spawnPoint.x(); + ipos.pos[1] = spawnPoint.y(); + ipos.pos[2] = spawnPoint.z(); + + if (!referenceObject.getClass().isActor()) + { + ipos.rot[0] = referenceObject.getRefData().getPosition().rot[0]; + ipos.rot[1] = referenceObject.getRefData().getPosition().rot[1]; + } + else + { + ipos.rot[0] = 0; + ipos.rot[1] = 0; + } + ipos.rot[2] = referenceObject.getRefData().getPosition().rot[2]; + + MWWorld::Ptr placed = copyObjectToCell(ptr, referenceCell, ipos, ptr.getRefData().getCount(), false); + placed.getClass().adjustPosition(placed, true); // snap to ground + return placed; + } + void World::indexToPosition (int cellX, int cellY, float &x, float &y, bool centre) const { const int cellSize = 8192; @@ -1368,10 +1429,10 @@ namespace MWWorld player = iter; continue; } - moveObjectImp(iter->first, iter->second.x(), iter->second.y(), iter->second.z()); + moveObjectImp(iter->first, iter->second.x(), iter->second.y(), iter->second.z(), false); } if(player != results.end()) - moveObjectImp(player->first, player->second.x(), player->second.y(), player->second.z()); + moveObjectImp(player->first, player->second.x(), player->second.y(), player->second.z(), false); mPhysics->debugDraw(); } @@ -1705,9 +1766,9 @@ namespace MWWorld mWeatherManager->modRegion(regionid, chances); } - osg::Vec2f World::getNorthVector (CellStore* cell) + osg::Vec2f World::getNorthVector (const CellStore* cell) { - MWWorld::Ptr northmarker = cell->search("northmarker"); + MWWorld::ConstPtr northmarker = cell->searchConst("northmarker"); if (northmarker.isEmpty()) return osg::Vec2f(0, 1); @@ -1744,6 +1805,8 @@ namespace MWWorld { cellid.mWorldspace = ref.mRef.getDestCell(); cellid.mPaged = false; + cellid.mIndex.mX = 0; + cellid.mIndex.mY = 0; } else { @@ -1992,7 +2055,7 @@ namespace MWWorld bool World::isUnderwater(const MWWorld::CellStore* cell, const osg::Vec3f &pos) const { - if (!(cell->getCell()->mData.mFlags & ESM::Cell::HasWater)) { + if (!(cell->getCell()->hasWater())) { return false; } return pos.z() < cell->getWaterLevel(); @@ -2330,14 +2393,17 @@ namespace MWWorld return mPhysics->getLineOfSight(actor, targetActor); } - float World::getDistToNearestRayHit(const osg::Vec3f& from, const osg::Vec3f& dir, float maxDist) + float World::getDistToNearestRayHit(const osg::Vec3f& from, const osg::Vec3f& dir, float maxDist, bool includeWater) { osg::Vec3f to (dir); to.normalize(); to = from + (to * maxDist); - MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(from, to, MWWorld::Ptr(), - MWPhysics::CollisionType_World|MWPhysics::CollisionType_HeightMap|MWPhysics::CollisionType_Door); + int collisionTypes = MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Door; + if (includeWater) { + collisionTypes |= MWPhysics::CollisionType_Water; + } + MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(from, to, MWWorld::Ptr(), collisionTypes); if (!result.mHit) return maxDist; @@ -2624,6 +2690,7 @@ namespace MWWorld void World::breakInvisibility(const Ptr &actor) { + actor.getClass().getCreatureStats(actor).getSpells().purgeEffect(ESM::MagicEffect::Invisibility); actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Invisibility); if (actor.getClass().hasInventoryStore(actor)) actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Invisibility); @@ -2647,7 +2714,7 @@ namespace MWWorld } } - bool World::findInteriorPositionInWorldSpace(MWWorld::CellStore* cell, osg::Vec3f& result) + bool World::findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result) { if (cell->isExterior()) return false; @@ -2996,6 +3063,14 @@ namespace MWWorld } } + bool World::isPlayerInJail() const + { + if (mGoToJail) + return true; + + return MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_Jail); + } + void World::spawnRandomCreature(const std::string &creatureList) { const ESM::CreatureLevList* list = getStore().get().find(creatureList); @@ -3009,23 +3084,9 @@ namespace MWWorld if (selectedCreature.empty()) return; - ESM::Position ipos = mPlayer->getPlayer().getRefData().getPosition(); - osg::Vec3f pos(ipos.asVec3()); - osg::Quat rot(-ipos.rot[2], osg::Vec3f(0,0,1)); - const float distance = 50; - pos = pos + (rot * osg::Vec3f(0,1,0)) * distance; - ipos.pos[0] = pos.x(); - ipos.pos[1] = pos.y(); - ipos.pos[2] = pos.z(); - ipos.rot[0] = 0; - ipos.rot[1] = 0; - ipos.rot[2] = 0; - - MWWorld::CellStore* cell = mPlayer->getPlayer().getCell(); MWWorld::ManualRef ref(getStore(), selectedCreature, 1); - ref.getPtr().getCellRef().setPosition(ipos); - safePlaceObject(ref.getPtr(), cell, ipos); + safePlaceObject(ref.getPtr(), getPlayerPtr(), getPlayerPtr().getCell(), 0, 220.f); } } @@ -3064,8 +3125,8 @@ namespace MWWorld mRendering->spawnEffect(model, textureOverride, worldPos); } - void World::explodeSpell(const osg::Vec3f &origin, const ESM::EffectList &effects, const Ptr &caster, ESM::RangeType rangeType, - const std::string& id, const std::string& sourceName) + void World::explodeSpell(const osg::Vec3f &origin, const ESM::EffectList &effects, const Ptr &caster, const Ptr& ignore, + ESM::RangeType rangeType, const std::string& id, const std::string& sourceName) { std::map > toApply; for (std::vector::const_iterator effectIt = effects.mList.begin(); @@ -3113,6 +3174,9 @@ namespace MWWorld if (apply->first == caster) continue; + if (apply->first == ignore) + continue; + if (source.isEmpty()) source = apply->first; @@ -3129,33 +3193,19 @@ namespace MWWorld void World::activate(const Ptr &object, const Ptr &actor) { - MWScript::InterpreterContext interpreterContext (&object.getRefData().getLocals(), object); - interpreterContext.activate (object); - - std::string script = object.getClass().getScript (object); - breakInvisibility(actor); - if (mScriptsEnabled) + if (object.getRefData().activate()) { - if (!script.empty()) - { - getLocalScripts().setIgnore (object); - MWBase::Environment::get().getScriptManager()->run (script, interpreterContext); - } - if (!interpreterContext.hasActivationBeenHandled()) - interpreterContext.executeActivation(object, actor); + boost::shared_ptr action = (object.getClass().activate(object, actor)); + action->execute (actor); } - else - interpreterContext.executeActivation(object, actor); } struct ResetActorsVisitor { bool operator() (Ptr ptr) { - // Can't reset actors that were moved to a different cell, because we don't know what cell they came from. - // This could be fixed once we properly track actor cell changes, but may not be desirable behaviour anyhow. if (ptr.getClass().isActor() && ptr.getCellRef().hasContentFile()) { const ESM::Position& origPos = ptr.getCellRef().getPosition(); @@ -3187,15 +3237,24 @@ namespace MWWorld osg::Vec3f World::aimToTarget(const ConstPtr &actor, const MWWorld::ConstPtr& target) { - osg::Vec3f weaponPos = getActorHeadTransform(actor).getTrans(); - osg::Vec3f targetPos = mPhysics->getPosition(target); + osg::Vec3f weaponPos = actor.getRefData().getPosition().asVec3(); + weaponPos.z() += mPhysics->getHalfExtents(actor).z() * 2 * 0.75; + osg::Vec3f targetPos = mPhysics->getCollisionObjectPosition(target); return (targetPos - weaponPos); } float World::getHitDistance(const ConstPtr &actor, const ConstPtr &target) { - osg::Vec3f weaponPos = getActorHeadTransform(actor).getTrans(); - return mPhysics->getHitDistance(weaponPos, target); + osg::Vec3f weaponPos = actor.getRefData().getPosition().asVec3(); + osg::Vec3f halfExtents = mPhysics->getHalfExtents(actor); + weaponPos.z() += halfExtents.z() * 2 * 0.75; + + return mPhysics->getHitDistance(weaponPos, target) - halfExtents.y(); + } + + void World::preloadCommonAssets() + { + mRendering->preloadCommonAssets(); } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 4ed97759ef..7af632d235 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -5,21 +5,20 @@ #include +#include +#include + +#include "../mwbase/world.hpp" + #include "ptr.hpp" #include "scene.hpp" #include "esmstore.hpp" #include "cells.hpp" #include "localscripts.hpp" #include "timestamp.hpp" -#include "fallback.hpp" #include "globals.hpp" - -#include "../mwbase/world.hpp" - #include "contentloader.hpp" -#include - namespace osg { class Group; @@ -71,7 +70,7 @@ namespace MWWorld { Resource::ResourceSystem* mResourceSystem; - MWWorld::Fallback mFallback; + Fallback::Map mFallback; MWRender::RenderingManager* mRendering; MWWorld::WeatherManager* mWeatherManager; @@ -120,7 +119,7 @@ namespace MWWorld void rotateObjectImp (const Ptr& ptr, const osg::Vec3f& rot, bool adjust); - Ptr moveObjectImp (const Ptr& ptr, float x, float y, float z); + Ptr moveObjectImp (const Ptr& ptr, float x, float y, float z, bool movePhysics=true); ///< @return an updated Ptr in case the Ptr's cell changes Ptr copyObjectToCell(const ConstPtr &ptr, CellStore* cell, ESM::Position pos, int count, bool adjustPos); @@ -184,6 +183,8 @@ namespace MWWorld virtual void startNewGame (bool bypass); ///< \param bypass Bypass regular game start. + virtual void preloadCommonAssets(); + virtual void clear(); virtual int countSavedGameRecords() const; @@ -210,7 +211,7 @@ namespace MWWorld virtual void adjustSky(); - virtual const Fallback *getFallback() const; + virtual const Fallback::Map *getFallback() const; virtual Player& getPlayer(); virtual MWWorld::Ptr getPlayerPtr(); @@ -228,7 +229,7 @@ namespace MWWorld virtual bool isCellQuasiExterior() const; - virtual osg::Vec2f getNorthVector (CellStore* cell); + virtual osg::Vec2f getNorthVector (const CellStore* cell); ///< get north vector for given interior cell virtual void getDoorMarkers (MWWorld::CellStore* cell, std::vector& out); @@ -323,15 +324,16 @@ namespace MWWorld virtual float getTimeScaleFactor() const; - virtual void changeToInteriorCell (const std::string& cellName, - const ESM::Position& position); + virtual void changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent = true); ///< Move to interior cell. + ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes - virtual void changeToExteriorCell (const ESM::Position& position); + virtual void changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent = true); ///< Move to exterior cell. + ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes - virtual void changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool detectWorldSpaceChange=true); - ///< @param detectWorldSpaceChange if true, clean up worldspace-specific data when the world space changes + virtual void changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent=true); + ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes virtual const ESM::Cell *getExterior (const std::string& cellName) const; ///< Return a cell matching the given name or a 0-pointer, if there is no such cell. @@ -354,7 +356,7 @@ namespace MWWorld virtual MWWorld::Ptr moveObject (const Ptr& ptr, float x, float y, float z); ///< @return an updated Ptr in case the Ptr's cell changes - virtual MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z); + virtual MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z, bool movePhysics=true); ///< @return an updated Ptr virtual void scaleObject (const Ptr& ptr, float scale); @@ -365,8 +367,12 @@ namespace MWWorld /// \param adjust indicates rotation should be set or adjusted virtual void rotateObject (const Ptr& ptr,float x,float y,float z, bool adjust = false); - virtual MWWorld::Ptr safePlaceObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos); - ///< place an object in a "safe" location (ie not in the void, etc). Makes a copy of the Ptr. + virtual MWWorld::Ptr placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos); + ///< Place an object. Makes a copy of the Ptr. + + virtual MWWorld::Ptr safePlaceObject (const MWWorld::ConstPtr& ptr, const MWWorld::ConstPtr& referenceObject, MWWorld::CellStore* referenceCell, int direction, float distance); + ///< Place an object in a safe place next to \a referenceObject. \a direction and \a distance specify the wanted placement + /// relative to \a referenceObject (but the object may be placed somewhere else if the wanted location is obstructed). virtual float getMaxActivationDistance(); @@ -521,7 +527,7 @@ namespace MWWorld virtual bool getLOS(const MWWorld::ConstPtr& actor,const MWWorld::ConstPtr& targetActor); ///< get Line of Sight (morrowind stupid implementation) - virtual float getDistToNearestRayHit(const osg::Vec3f& from, const osg::Vec3f& dir, float maxDist); + virtual float getDistToNearestRayHit(const osg::Vec3f& from, const osg::Vec3f& dir, float maxDist, bool includeWater = false); virtual void enableActorCollision(const MWWorld::Ptr& actor, bool enable); @@ -593,7 +599,7 @@ namespace MWWorld // Are we in an exterior or pseudo-exterior cell and it's night? virtual bool isDark() const; - virtual bool findInteriorPositionInWorldSpace(MWWorld::CellStore* cell, osg::Vec3f& result); + virtual bool findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result); /// Teleports \a ptr to the closest reference of \a id (e.g. DivineMarker, PrisonMarker, TempleMarker) /// @note id must be lower case @@ -623,8 +629,8 @@ namespace MWWorld virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos); - virtual void explodeSpell (const osg::Vec3f& origin, const ESM::EffectList& effects, - const MWWorld::Ptr& caster, ESM::RangeType rangeType, const std::string& id, const std::string& sourceName); + virtual void explodeSpell (const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, + const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id, const std::string& sourceName); virtual void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor); @@ -645,6 +651,8 @@ namespace MWWorld /// Return the distance between actor's weapon and target's collision box. virtual float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target); + + virtual bool isPlayerInJail() const; }; } diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 2300f97a36..24ea1835aa 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -9,6 +9,8 @@ if (GTEST_FOUND) mwworld/test_store.cpp mwdialogue/test_keywordsearch.cpp + + esm/test_fixed_string.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/esm/test_fixed_string.cpp b/apps/openmw_test_suite/esm/test_fixed_string.cpp new file mode 100644 index 0000000000..59dd7fe66a --- /dev/null +++ b/apps/openmw_test_suite/esm/test_fixed_string.cpp @@ -0,0 +1,75 @@ +#include +#include "components/esm/esmcommon.hpp" + +TEST(EsmFixedString, operator__eq_ne) +{ + { + SCOPED_TRACE("asdc == asdc"); + ESM::NAME name; + name.assign("asdc"); + char s[4] = {'a', 's', 'd', 'c'}; + std::string ss(s, 4); + + EXPECT_TRUE(name == s); + EXPECT_TRUE(name == ss.c_str()); + EXPECT_TRUE(name == ss); + } + { + SCOPED_TRACE("asdc == asdcx"); + ESM::NAME name; + name.assign("asdc"); + char s[5] = {'a', 's', 'd', 'c', 'x'}; + std::string ss(s, 5); + + EXPECT_TRUE(name != s); + EXPECT_TRUE(name != ss.c_str()); + EXPECT_TRUE(name != ss); + } + { + SCOPED_TRACE("asdc == asdc[NULL]"); + ESM::NAME name; + name.assign("asdc"); + char s[5] = {'a', 's', 'd', 'c', '\0'}; + std::string ss(s, 5); + + EXPECT_TRUE(name == s); + EXPECT_TRUE(name == ss.c_str()); + EXPECT_TRUE(name == ss); + } +} + +TEST(EsmFixedString, empty_strings) +{ + { + SCOPED_TRACE("4 bytes"); + ESM::NAME empty = ESM::NAME(); + EXPECT_TRUE(empty == ""); + EXPECT_TRUE(empty == static_cast(0)); + EXPECT_TRUE(empty != "some string"); + EXPECT_TRUE(empty != static_cast(42)); + } + { + SCOPED_TRACE("32 bytes"); + ESM::NAME32 empty = ESM::NAME32(); + EXPECT_TRUE(empty == ""); + EXPECT_TRUE(empty != "some string"); + } +} + +TEST(EsmFixedString, struct_size) +{ + ASSERT_EQ(4, sizeof(ESM::NAME)); + ASSERT_EQ(32, sizeof(ESM::NAME32)); + ASSERT_EQ(64, sizeof(ESM::NAME64)); + ASSERT_EQ(256, sizeof(ESM::NAME256)); +} + +TEST(EsmFixedString, DISABLED_is_pod) +{ + /* TODO: enable in C++11 + * ASSERT_TRUE(std::is_pod::value); + * ASSERT_TRUE(std::is_pod::value); + * ASSERT_TRUE(std::is_pod::value); + * ASSERT_TRUE(std::is_pod::value); + */ +} diff --git a/apps/wizard/CMakeLists.txt b/apps/wizard/CMakeLists.txt index 89438640c1..dcd9444909 100644 --- a/apps/wizard/CMakeLists.txt +++ b/apps/wizard/CMakeLists.txt @@ -1,13 +1,3 @@ -if (WIN32) # windows users can just run the morrowind installer - set(OPENMW_USE_UNSHIELD FALSE) -else() - set(OPENMW_USE_UNSHIELD TRUE) - - find_package(LIBUNSHIELD REQUIRED) - if(NOT LIBUNSHIELD_FOUND) - message(FATAL_ERROR "Failed to find Unshield library") - endif(NOT LIBUNSHIELD_FOUND) -endif() set(WIZARD componentselectionpage.cpp @@ -103,7 +93,7 @@ endif() include_directories(${CMAKE_CURRENT_BINARY_DIR}) if (OPENMW_USE_UNSHIELD) - include_directories(${LIBUNSHIELD_INCLUDE_DIR}) + include_directories(${LIBUNSHIELD_INCLUDE_DIRS}) endif() add_executable(openmw-wizard @@ -129,13 +119,10 @@ if (DESIRED_QT_VERSION MATCHES 4) endif() else() qt5_use_modules(openmw-wizard Widgets Core) - if (WIN32) - target_link_libraries(Qt5::WinMain) - endif() endif() if (OPENMW_USE_UNSHIELD) - target_link_libraries(openmw-wizard ${LIBUNSHIELD_LIBRARY}) + target_link_libraries(openmw-wizard ${LIBUNSHIELD_LIBRARIES}) endif() diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000000..1bc0d3ca69 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,54 @@ +version: "{build}" + +branches: + only: + - master + - /openmw-.*$/ + - appveyor + +platform: + - Win32 +# - x64 + +configuration: Debug + +matrix: + fast_finish: true + +# For the Qt, Boost, CMake, etc installs +os: unstable + +# We want the git revision for versioning, +# so shallow clones don't work. +clone_depth: 1 + +cache: + - C:\projects\openmw\deps\Bullet-2.83.5-win32.7z + - C:\projects\openmw\deps\Bullet-2.83.5-win64.7z + - C:\projects\openmw\deps\MyGUI-3.2.2-win32.7z + - C:\projects\openmw\deps\MyGUI-3.2.2-win64.7z + - C:\projects\openmw\deps\OSG-3.3.8-win32.7z + - C:\projects\openmw\deps\OSG-3.3.8-win64.7z + - C:\projects\openmw\deps\ffmpeg32-2.5.2.7z + - C:\projects\openmw\deps\ffmpeg32-2.5.2-dev.7z + - C:\projects\openmw\deps\ffmpeg64-2.5.2.7z + - C:\projects\openmw\deps\ffmpeg64-2.5.2-dev.7z + +clone_folder: C:\projects\openmw + +before_build: + - cmd: sh %APPVEYOR_BUILD_FOLDER%\CI\before_script.msvc.sh -u -p %PLATFORM% + +build_script: + - cmd: if %PLATFORM%==Win32 set build=Build_32 + - cmd: if %PLATFORM%==x64 set build=Build_64 + - cmd: msbuild %build%\OpenMW.sln /t:Build /p:Configuration=%configuration% /m:2 /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" + +test: off + +#notifications: +# - provider: Email +# to: +# - +# on_build_failure: true +# on_build_status_changed: true diff --git a/cmake/FindBullet.cmake b/cmake/FindBullet.cmake index 6d5c517afd..6d68bed707 100644 --- a/cmake/FindBullet.cmake +++ b/cmake/FindBullet.cmake @@ -1,78 +1,73 @@ # - Try to find the Bullet physics engine # -# This module defines the following variables -# -# BULLET_FOUND - Was bullet found -# BULLET_INCLUDE_DIRS - the Bullet include directories -# BULLET_LIBRARIES - Link to this, by default it includes -# all bullet components (Dynamics, -# Collision, LinearMath, & SoftBody) -# -# This module accepts the following variables -# +# This module accepts the following env variables # BULLET_ROOT - Can be set to bullet install path or Windows build path # - +# Once done this will define +# Bullet_FOUND - System has the all required components. +# Bullet_INCLUDE_DIRS - Include directory necessary for using the required components headers. +# Bullet_LIBRARIES - Link these to use the required bullet components. +# Bullet_VERSION - Version of libbullet +# +# For each of the components +# - LinearMath +# - BulletCollision +# - BulletSoftBody +# - BulletDynamics +# # Copyright (c) 2009, Philip Lowman +# Modified for OpenMW to parse BT_BULLET_VERSION. # # Redistribution AND use is allowed according to the terms of the New # BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. -set(BULLET_ROOT $ENV{BULLET_ROOT}) +include(LibFindMacros) -macro(_FIND_BULLET_LIBRARY _var) - find_library(${_var} - NAMES - ${ARGN} - PATHS - ${BULLET_ROOT} - ${BULLET_ROOT}/lib/Debug - ${BULLET_ROOT}/lib/Release - ${BULLET_ROOT}/out/release8/libs - ${BULLET_ROOT}/out/debug8/libs - PATH_SUFFIXES lib - ) - mark_as_advanced(${_var}) +# Macro: _internal_find_bullet_library +# Checks for the given component by invoking pkgconfig etc. +macro(_internal_find_bullet_library _lib) + libfind_pkg_detect(Bullet_${_lib} bullet + FIND_LIBRARY ${_lib} + HINTS $ENV{BULLET_ROOT} + PATH_SUFFIXES lib + ) + libfind_process(Bullet_${_lib}) endmacro() -macro(_BULLET_APPEND_LIBRARIES _list _release) - set(_debug ${_release}_DEBUG) - if(${_debug}) - set(${_list} ${${_list}} optimized ${${_release}} debug ${${_debug}}) - else() - set(${_list} ${${_list}} ${${_release}}) - endif() -endmacro() +set(_known_components LinearMath BulletCollision BulletSoftBody BulletDynamics) -find_path(BULLET_INCLUDE_DIR NAMES btBulletCollisionCommon.h - PATHS - ${BULLET_ROOT}/include - ${BULLET_ROOT}/src - PATH_SUFFIXES bullet +# Check if the required components were found and add their stuff to the Bullet_* vars. +foreach (_component ${Bullet_FIND_COMPONENTS}) + list(FIND _known_components ${_component} _known_component) + if (_known_component EQUAL -1) + message(FATAL_ERROR "Unknown component '${_component}'") + endif() + + set(Bullet_${_component}_Debug_FIND_QUIETLY TRUE) # don't spam messages with optional Debug component + _internal_find_bullet_library(${_component}) + _internal_find_bullet_library(${_component}_Debug) + + if (Bullet_${_component}_Debug_FOUND) + set(Bullet_LIBRARIES ${Bullet_LIBRARIES} optimized ${Bullet_${_component}_LIBRARIES} debug ${Bullet_${_component}_Debug_LIBRARIES}) + else() + set(Bullet_LIBRARIES ${Bullet_LIBRARIES} ${Bullet_${_component}_LIBRARIES}) + endif() +endforeach() + +libfind_pkg_detect(Bullet bullet + FIND_PATH btBulletCollisionCommon.h + HINTS $ENV{BULLET_ROOT} + PATH_SUFFIXES include/bullet ) +set(Bullet_INCLUDE_DIRS ${Bullet_INCLUDE_DIR}) +libfind_version_header(Bullet LinearMath/btScalar.h BT_BULLET_VERSION) -# Find the libraries - -#_FIND_BULLET_LIBRARY(BULLET_DYNAMICS_LIBRARY BulletDynamics) -#_FIND_BULLET_LIBRARY(BULLET_DYNAMICS_LIBRARY_DEBUG BulletDynamics_Debug BulletDynamics_d) -_FIND_BULLET_LIBRARY(BULLET_COLLISION_LIBRARY BulletCollision) -_FIND_BULLET_LIBRARY(BULLET_COLLISION_LIBRARY_DEBUG BulletCollision_Debug BulletCollision_d) -_FIND_BULLET_LIBRARY(BULLET_MATH_LIBRARY BulletMath LinearMath) -_FIND_BULLET_LIBRARY(BULLET_MATH_LIBRARY_DEBUG BulletMath_Debug BulletMath_d LinearMath_debug LinearMath_d) - - -# handle the QUIETLY and REQUIRED arguments and set BULLET_FOUND to TRUE if -# all listed variables are TRUE -include(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(Bullet DEFAULT_MSG - #BULLET_DYNAMICS_LIBRARY - BULLET_COLLISION_LIBRARY BULLET_MATH_LIBRARY - BULLET_INCLUDE_DIR) - -set(BULLET_INCLUDE_DIRS ${BULLET_INCLUDE_DIR}) -if(BULLET_FOUND) - #_BULLET_APPEND_LIBRARIES(BULLET_LIBRARIES BULLET_DYNAMICS_LIBRARY) - _BULLET_APPEND_LIBRARIES(BULLET_LIBRARIES BULLET_COLLISION_LIBRARY) - _BULLET_APPEND_LIBRARIES(BULLET_LIBRARIES BULLET_MATH_LIBRARY) -endif() +FIND_PACKAGE_HANDLE_STANDARD_ARGS(Bullet + FOUND_VAR Bullet_FOUND + VERSION_VAR Bullet_VERSION + HANDLE_COMPONENTS + REQUIRED_VARS + Bullet_LIBRARIES + Bullet_INCLUDE_DIR +) diff --git a/cmake/FindFFmpeg.cmake b/cmake/FindFFmpeg.cmake index 74584bf318..830030e32d 100644 --- a/cmake/FindFFmpeg.cmake +++ b/cmake/FindFFmpeg.cmake @@ -1,11 +1,14 @@ # vim: ts=2 sw=2 -# - Try to find the required ffmpeg components(default: AVFORMAT, AVUTIL, AVCODEC) +# - Try to find the required ffmpeg components +# +# This module accepts the following env variable +# FFMPEG_HOME - Can be set to custom install path # # Once done this will define -# FFMPEG_FOUND - System has the all required components. -# FFMPEG_INCLUDE_DIRS - Include directory necessary for using the required components headers. -# FFMPEG_LIBRARIES - Link these to use the required ffmpeg components. -# FFMPEG_DEFINITIONS - Compiler switches required for using the required ffmpeg components. +# FFmpeg_FOUND - System has the all required components. +# FFmpeg_INCLUDE_DIRS - Include directory necessary for using the required components headers. +# FFmpeg_LIBRARIES - Link these to use the required ffmpeg components. +# FFmpeg_DEFINITIONS - Compiler switches required for using the required ffmpeg components. # # For each of the components it will additionaly set. # - AVCODEC @@ -16,144 +19,96 @@ # - SWSCALE # - SWRESAMPLE # the following variables will be defined -# _FOUND - System has -# _INCLUDE_DIRS - Include directory necessary for using the headers -# _LIBRARIES - Link these to use -# _DEFINITIONS - Compiler switches required for using -# _VERSION - The components version +# FFmpeg__FOUND - System has +# FFmpeg__INCLUDE_DIRS - Include directory necessary for using the headers +# FFmpeg__LIBRARIES - Link these to use +# FFmpeg__DEFINITIONS - Compiler switches required for using +# FFmpeg__VERSION - The components version # # Copyright (c) 2006, Matthias Kretz, # Copyright (c) 2008, Alexander Neundorf, # Copyright (c) 2011, Michael Jansen, +# Copyright (c) 2016, Roman Proskuryakov, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. +include(LibFindMacros) include(FindPackageHandleStandardArgs) -# The default components were taken from a survey over other FindFFMPEG.cmake files -if (NOT FFmpeg_FIND_COMPONENTS) - set(FFmpeg_FIND_COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE) -endif () - -# -### Macro: set_component_found -# -# Marks the given component as found if both *_LIBRARIES AND *_INCLUDE_DIRS is present. -# -macro(set_component_found _component ) - if (${_component}_LIBRARIES AND ${_component}_INCLUDE_DIRS) - # message(STATUS " - ${_component} found.") - set(${_component}_FOUND TRUE) - else () - # message(STATUS " - ${_component} not found.") - endif () -endmacro() - -# -### Macro: find_component -# -# Checks for the given component by invoking pkgconfig and then looking up the libraries and -# include directories. -# -macro(find_component _component _pkgconfig _library _header) - - if (NOT WIN32) - # use pkg-config to get the directories and then use these values - # in the FIND_PATH() and FIND_LIBRARY() calls - find_package(PkgConfig) - if (PKG_CONFIG_FOUND) - pkg_check_modules(PC_${_component} ${_pkgconfig}) - endif () - endif (NOT WIN32) - - find_path(${_component}_INCLUDE_DIRS ${_header} - HINTS - ${FFMPEGSDK_INC} - ${PC_LIB${_component}_INCLUDEDIR} - ${PC_LIB${_component}_INCLUDE_DIRS} - PATH_SUFFIXES - ffmpeg - ) - - find_library(${_component}_LIBRARIES NAMES ${_library} - HINTS - ${FFMPEGSDK_LIB} - ${PC_LIB${_component}_LIBDIR} - ${PC_LIB${_component}_LIBRARY_DIRS} - ) - - set(${_component}_DEFINITIONS ${PC_${_component}_CFLAGS_OTHER} CACHE STRING "The ${_component} CFLAGS.") - set(${_component}_VERSION ${PC_${_component}_VERSION} CACHE STRING "The ${_component} version number.") - - set_component_found(${_component}) - - mark_as_advanced( - ${_component}_INCLUDE_DIRS - ${_component}_LIBRARIES - ${_component}_DEFINITIONS - ${_component}_VERSION) - +# Macro: _internal_find_component +# Checks for the given component by invoking pkgconfig etc. +macro(_internal_find_component _component _pkgconfig _library _header) + set(_package_component FFmpeg_${_component}) + libfind_pkg_detect(${_package_component} ${_pkgconfig} + FIND_PATH ${_header} + HINTS $ENV{FFMPEG_HOME} + PATH_SUFFIXES include ffmpeg + FIND_LIBRARY ${_library} + HINTS $ENV{FFMPEG_HOME} + PATH_SUFFIXES lib + ) + set(${_package_component}_DEFINITIONS ${${_package_component}_PKGCONF_CFLAGS_OTHER}) + set(${_package_component}_VERSION ${${_package_component}_PKGCONF_VERSION}) + libfind_process(${_package_component}) endmacro() -# Check for cached results. If there are skip the costly part. -if (NOT FFMPEG_LIBRARIES) +# setter for 'hashmap' +macro(hashmap_set _table _key) # ARGN + set(${_table}_${_key} ${ARGN}) +endmacro() - set (FFMPEGSDK $ENV{FFMPEG_HOME}) - if (FFMPEGSDK) - set (FFMPEGSDK_INC "${FFMPEGSDK}/include") - set (FFMPEGSDK_LIB "${FFMPEGSDK}/lib") - endif () +# check for key in 'hashmap' +macro(hashmap_exists _table _key _out_var) + if (DEFINED ${_table}_${_key}) + set(${_out_var} TRUE) + else() + set(${_out_var} FALSE) + endif() +endmacro() - # Check for all possible component. - find_component(AVCODEC libavcodec avcodec libavcodec/avcodec.h) - find_component(AVFORMAT libavformat avformat libavformat/avformat.h) - find_component(AVDEVICE libavdevice avdevice libavdevice/avdevice.h) - find_component(AVUTIL libavutil avutil libavutil/avutil.h) - find_component(SWSCALE libswscale swscale libswscale/swscale.h) - find_component(POSTPROC libpostproc postproc libpostproc/postprocess.h) - find_component(SWRESAMPLE libswresample swresample libswresample/swresample.h) - find_component(AVRESAMPLE libavresample avresample libavresample/avresample.h) +# getter for 'hashmap' +macro(hashmap_get _table _key _out_var) + set(${_out_var} ${${_table}_${_key}}) +endmacro() - # Check if the required components were found and add their stuff to the FFMPEG_* vars. - foreach (_component ${FFmpeg_FIND_COMPONENTS}) - if (${_component}_FOUND) - # message(STATUS "Required component ${_component} present.") - set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${${_component}_LIBRARIES}) - set(FFMPEG_DEFINITIONS ${FFMPEG_DEFINITIONS} ${${_component}_DEFINITIONS}) - list(APPEND FFMPEG_INCLUDE_DIRS ${${_component}_INCLUDE_DIRS}) - else () - # message(STATUS "Required component ${_component} missing.") - endif () - endforeach () - # Build the include path with duplicates removed. - if (FFMPEG_INCLUDE_DIRS) - list(REMOVE_DUPLICATES FFMPEG_INCLUDE_DIRS) - endif () +# fill 'hashmap' named find_args +hashmap_set(find_args AVCODEC libavcodec avcodec libavcodec/avcodec.h) +hashmap_set(find_args AVFORMAT libavformat avformat libavformat/avformat.h) +hashmap_set(find_args AVDEVICE libavdevice avdevice libavdevice/avdevice.h) +hashmap_set(find_args AVUTIL libavutil avutil libavutil/avutil.h) +hashmap_set(find_args SWSCALE libswscale swscale libswscale/swscale.h) +hashmap_set(find_args POSTPROC libpostproc postproc libpostproc/postprocess.h) +hashmap_set(find_args SWRESAMPLE libswresample swresample libswresample/swresample.h) +hashmap_set(find_args AVRESAMPLE libavresample avresample libavresample/avresample.h) - # cache the vars. - set(FFMPEG_INCLUDE_DIRS ${FFMPEG_INCLUDE_DIRS} CACHE STRING "The FFmpeg include directories." FORCE) - set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} CACHE STRING "The FFmpeg libraries." FORCE) - set(FFMPEG_DEFINITIONS ${FFMPEG_DEFINITIONS} CACHE STRING "The FFmpeg cflags." FORCE) - - mark_as_advanced(FFMPEG_INCLUDE_DIRS - FFMPEG_LIBRARIES - FFMPEG_DEFINITIONS) - -endif () - -# Now set the noncached _FOUND vars for the components. -foreach (_component AVCODEC AVDEVICE AVFORMAT AVUTIL POSTPROCESS SWSCALE SWRESAMPLE AVRESAMPLE) - set_component_found(${_component}) -endforeach () - -# Compile the list of required vars -set(_FFmpeg_REQUIRED_VARS FFMPEG_LIBRARIES FFMPEG_INCLUDE_DIRS) +# Check if the required components were found and add their stuff to the FFmpeg_* vars. foreach (_component ${FFmpeg_FIND_COMPONENTS}) - list(APPEND _FFmpeg_REQUIRED_VARS ${_component}_LIBRARIES ${_component}_INCLUDE_DIRS) + hashmap_exists(find_args ${_component} _known_component) + if (NOT _known_component) + message(FATAL_ERROR "Unknown component '${_component}'") + endif() + hashmap_get(find_args ${_component} _component_find_args) + _internal_find_component(${_component} ${_component_find_args}) + set(_package_component FFmpeg_${_component}) + if (${_package_component}_FOUND) + list(APPEND FFmpeg_LIBRARIES ${${_package_component}_LIBRARIES}) + list(APPEND FFmpeg_INCLUDE_DIRS ${${_package_component}_INCLUDE_DIRS}) + list(APPEND FFmpeg_DEFINITIONS ${${_package_component}_DEFINITIONS}) + endif () endforeach () -# Give a nice error message if some of the required vars are missing. -find_package_handle_standard_args(FFmpeg DEFAULT_MSG ${_FFmpeg_REQUIRED_VARS}) +# Build the include path with duplicates removed. +if (FFmpeg_INCLUDE_DIRS) + list(REMOVE_DUPLICATES FFmpeg_INCLUDE_DIRS) +endif() + +FIND_PACKAGE_HANDLE_STANDARD_ARGS(FFmpeg + FOUND_VAR FFmpeg_FOUND + HANDLE_COMPONENTS + REQUIRED_VARS + FFmpeg_LIBRARIES + FFmpeg_INCLUDE_DIRS +) diff --git a/cmake/FindFreetype.cmake b/cmake/FindFreetype.cmake index fc36d548e2..c154628da5 100644 --- a/cmake/FindFreetype.cmake +++ b/cmake/FindFreetype.cmake @@ -8,62 +8,93 @@ #------------------------------------------------------------------- # - Try to find FreeType +# +# This module accepts the following env variable +# FREETYPE_DIR - Can be set to custom install path +# # Once done, this will define # -# FREETYPE_FOUND - system has FreeType -# FREETYPE_INCLUDE_DIRS - the FreeType include directories -# FREETYPE_LIBRARIES - link these to use FreeType +# Freetype_FOUND - system has FreeType +# Freetype_INCLUDE_DIRS - the FreeType include directories +# Freetype_LIBRARIES - link these to use FreeType +# Freetype_VERSION - version of FreeType +# +# libfreetype internals: +# +# ====================================== +# new versions (2.5.2) +# +# file structure: +# /include/freetype2/ft2build.h +# /include/freetype2/freetype.h +# used as: +# #include +# #include +# requires: +# -I /include/freetype2/ +# +# ====================================== +# old versions (2.4.8, 2.3.5) +# +# file structure: +# /include/ft2build.h +# /include/freetype2/freetype/freetype.h +# used as: +# #include +# #include +# requires: +# -I /include/ -I /include/freetype2/ +# +# ====================================== -include(FindPkgMacros) -findpkg_begin(FREETYPE) +include(LibFindMacros) +include(PreprocessorUtils) -# Get path, convert backslashes as ${ENV_${var}} -getenv_path(FREETYPE_HOME) - -# construct search paths -set(FREETYPE_PREFIX_PATH ${FREETYPE_HOME} ${ENV_FREETYPE_HOME}) -create_search_paths(FREETYPE) -# redo search if prefix path changed -clear_if_changed(FREETYPE_PREFIX_PATH - FREETYPE_LIBRARY_FWK - FREETYPE_LIBRARY_REL - FREETYPE_LIBRARY_DBG - FREETYPE_INCLUDE_DIR +set(_REGULAR_INSTALL_PATHS + /usr/X11R6 + /usr/local/X11R6 + /usr/local/X11 + /usr/freeware + ENV GTKMM_BASEPATH + [HKEY_CURRENT_USER\\SOFTWARE\\gtkmm\\2.4;Path] + [HKEY_LOCAL_MACHINE\\SOFTWARE\\gtkmm\\2.4;Path] ) -set(FREETYPE_LIBRARY_NAMES freetype2311 freetype239 freetype238 freetype235 freetype219 freetype) -get_debug_names(FREETYPE_LIBRARY_NAMES) +libfind_pkg_detect(Freetype freetype2 + FIND_PATH ft2build.h + HINTS $ENV{FREETYPE_DIR} + PATHS ${_REGULAR_INSTALL_PATHS} + PATH_SUFFIXES include freetype2 + FIND_LIBRARY freetype freetype2311 freetype239 freetype238 freetype235 freetype219 + HINTS $ENV{FREETYPE_DIR} + PATHS ${_REGULAR_INSTALL_PATHS} + PATH_SUFFIXES lib +) +find_path(Freetype_OLD_INCLUDE_DIR + # in new versions of freetype old_include_dir equals to include_dir + # see explanation above + NAMES freetype/freetype.h freetype.h + PATHS ${Freetype_INCLUDE_DIR} + PATH_SUFFIXES freetype2 + NO_DEFAULT_PATH +) -use_pkgconfig(FREETYPE_PKGC freetype2) +# get version from freetype.h +find_file(Freetype_HEADER + NAMES freetype.h + PATH_SUFFIXES freetype + PATHS ${Freetype_OLD_INCLUDE_DIR} +) +if (Freetype_HEADER) + get_version_from_n_defines(Freetype_VERSION + ${Freetype_HEADER} + FREETYPE_MAJOR FREETYPE_MINOR FREETYPE_PATCH + ) +endif() -# prefer static library over framework -set(CMAKE_FIND_FRAMEWORK "LAST") +set(Freetype_PROCESS_INCLUDES Freetype_INCLUDE_DIR Freetype_OLD_INCLUDE_DIR) +libfind_process(Freetype) -message(STATUS "CMAKE_PREFIX_PATH: ${CMAKE_PREFIX_PATH}") -findpkg_framework(FREETYPE) -message(STATUS "CMAKE_PREFIX_PATH: ${CMAKE_PREFIX_PATH}") - -find_path(FREETYPE_INCLUDE_DIR NAMES freetype/freetype.h HINTS ${FREETYPE_INC_SEARCH_PATH} ${FREETYPE_PKGC_INCLUDE_DIRS} PATH_SUFFIXES freetype2) -find_path(FREETYPE_FT2BUILD_INCLUDE_DIR NAMES ft2build.h HINTS ${FREETYPE_INC_SEARCH_PATH} ${FREETYPE_PKGC_INCLUDE_DIRS}) - -if (SYMBIAN) -set(ORIGINAL_CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH}) -set(CMAKE_PREFIX_PATH ${CMAKE_SYSYEM_OUT_DIR}) -message(STATUS "Lib will be searched in Symbian out dir: ${CMAKE_SYSYEM_OUT_DIR}") -endif (SYMBIAN) -find_library(FREETYPE_LIBRARY_REL NAMES ${FREETYPE_LIBRARY_NAMES} HINTS ${FREETYPE_LIB_SEARCH_PATH} ${FREETYPE_PKGC_LIBRARY_DIRS} PATH_SUFFIXES "" release relwithdebinfo minsizerel) -find_library(FREETYPE_LIBRARY_DBG NAMES ${FREETYPE_LIBRARY_NAMES_DBG} HINTS ${FREETYPE_LIB_SEARCH_PATH} ${FREETYPE_PKGC_LIBRARY_DIRS} PATH_SUFFIXES "" debug) -if (SYMBIAN) -set(CMAKE_PREFIX_PATH ${ORIGINAL_CMAKE_PREFIX_PATH}) -endif (SYMBIAN) - -make_library_set(FREETYPE_LIBRARY) - -findpkg_finish(FREETYPE) -mark_as_advanced(FREETYPE_FT2BUILD_INCLUDE_DIR) -if (NOT FREETYPE_FT2BUILD_INCLUDE_DIR STREQUAL FREETYPE_INCLUDE_DIR) - set(FREETYPE_INCLUDE_DIRS ${FREETYPE_INCLUDE_DIRS} ${FREETYPE_FT2BUILD_INCLUDE_DIR}) -endif () - -# Reset framework finding -set(CMAKE_FIND_FRAMEWORK "FIRST") +if (Freetype_INCLUDE_DIRS) + list(REMOVE_DUPLICATES Freetype_INCLUDE_DIRS) +endif() diff --git a/cmake/FindLIBUNSHIELD.cmake b/cmake/FindLIBUNSHIELD.cmake index f0fa4cc824..285740b635 100644 --- a/cmake/FindLIBUNSHIELD.cmake +++ b/cmake/FindLIBUNSHIELD.cmake @@ -1,8 +1,8 @@ # Locate LIBUNSHIELD # This module defines -# LIBUNSHIELD_LIBRARY +# LIBUNSHIELD_LIBRARIES # LIBUNSHIELD_FOUND, if false, do not try to link to LibUnshield -# LIBUNSHIELD_INCLUDE_DIR, where to find the headers +# LIBUNSHIELD_INCLUDE_DIRS, where to find the headers # # Created by Tom Mason (wheybags) for OpenMW (http://openmw.org), based on FindMPG123.cmake # @@ -12,37 +12,24 @@ # function. Do we really need to repeat this stuff for every single # library when they all work the same? -FIND_PATH(LIBUNSHIELD_INCLUDE_DIR libunshield.h - HINTS - PATHS - ~/Library/Frameworks - /Library/Frameworks - /usr/local - /usr - /sw # Fink - /opt/local # DarwinPorts - /opt/csw # Blastwave - /opt - /usr/include +include(LibFindMacros) + +set(POSSIBLE_LOCATIONS + ~/Library/Frameworks + /Library/Frameworks + /usr/local + /usr + /sw # Fink + /opt/local # DarwinPorts + /opt/csw # Blastwave + /opt + /usr/include ) -FIND_LIBRARY(LIBUNSHIELD_LIBRARY - unshield - HINTS -# PATH_SUFFIXES lib64 lib libs64 libs libs/Win32 libs/Win64 - PATHS - ~/Library/Frameworks - /Library/Frameworks - /usr/local - /usr - /sw - /opt/local - /opt/csw - /opt - /usr/lib +libfind_pkg_detect(LIBUNSHIELD libunshield + FIND_PATH libunshield.h + HINTS ${POSSIBLE_LOCATIONS} + FIND_LIBRARY unshield + HINTS ${POSSIBLE_LOCATIONS} ) - -IF(LIBUNSHIELD_LIBRARY AND LIBUNSHIELD_INCLUDE_DIR) - SET(LIBUNSHIELD_FOUND "YES") -ENDIF(LIBUNSHIELD_LIBRARY AND LIBUNSHIELD_INCLUDE_DIR) - +libfind_process(LIBUNSHIELD) diff --git a/cmake/FindMyGUI.cmake b/cmake/FindMyGUI.cmake index 6e93a92ce6..0b3ac730ef 100644 --- a/cmake/FindMyGUI.cmake +++ b/cmake/FindMyGUI.cmake @@ -54,7 +54,7 @@ IF (WIN32) #Windows if ( MYGUI_STATIC ) set(LIB_SUFFIX "Static") - find_package(freetype) + find_package(Freetype) endif ( MYGUI_STATIC ) find_library ( MYGUI_LIBRARIES_REL NAMES MyGUIEngine${LIB_SUFFIX}.lib HINTS ${MYGUI_LIB_DIR} PATH_SUFFIXES "" release relwithdebinfo minsizerel ) @@ -71,7 +71,7 @@ ELSE (WIN32) #Unix FIND_PACKAGE(PkgConfig) IF(MYGUI_STATIC) # don't use pkgconfig on OS X, find freetype & append it's libs to resulting MYGUI_LIBRARIES - IF (NOT APPLE) + IF (NOT APPLE AND NOT ANDROID) PKG_SEARCH_MODULE(MYGUI MYGUIStatic MyGUIStatic) IF (MYGUI_INCLUDE_DIRS) SET(MYGUI_INCLUDE_DIRS ${MYGUI_INCLUDE_DIRS}) @@ -84,15 +84,15 @@ ELSE (WIN32) #Unix STRING(REGEX REPLACE "(.*)/.*" "\\1" MYGUI_LIB_DIR "${MYGUI_LIB_DIR}") STRING(REGEX REPLACE ".*/" "" MYGUI_LIBRARIES "${MYGUI_LIBRARIES}") ENDIF (MYGUI_INCLUDE_DIRS) - ELSE (NOT APPLE) + ELSE (NOT APPLE AND NOT ANDROID) SET(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${MYGUI_DEPENDENCIES_DIR}) - FIND_PACKAGE(freetype) + FIND_PACKAGE(Freetype REQUIRED) FIND_PATH(MYGUI_INCLUDE_DIRS MyGUI.h PATHS /usr/local/include /usr/include PATH_SUFFIXES MyGUI MYGUI) - FIND_LIBRARY(MYGUI_LIBRARIES MyGUIEngineStatic PATHS /usr/lib /usr/local/lib) + FIND_LIBRARY(MYGUI_LIBRARIES MyGUIEngineStatic PATHS /usr/lib /usr/local/lib ${OPENMW_DEPENDENCIES_DIR}) SET(MYGUI_LIB_DIR ${MYGUI_LIBRARIES}) STRING(REGEX REPLACE "(.*)/.*" "\\1" MYGUI_LIB_DIR "${MYGUI_LIB_DIR}") STRING(REGEX REPLACE ".*/" "" MYGUI_LIBRARIES "${MYGUI_LIBRARIES}") - ENDIF (NOT APPLE) + ENDIF (NOT APPLE AND NOT ANDROID) ELSE(MYGUI_STATIC) PKG_SEARCH_MODULE(MYGUI MYGUI MyGUI) IF (MYGUI_INCLUDE_DIRS) @@ -101,7 +101,7 @@ ELSE (WIN32) #Unix SET(MYGUI_LIBRARIES ${MYGUI_LIBRARIES} CACHE STRING "") ELSE (MYGUI_INCLUDE_DIRS) FIND_PATH(MYGUI_INCLUDE_DIRS MyGUI.h PATHS /usr/local/include /usr/include PATH_SUFFIXES MyGUI MYGUI) - FIND_LIBRARY(MYGUI_LIBRARIES mygui PATHS /usr/lib /usr/local/lib) + FIND_LIBRARY(MYGUI_LIBRARIES MyGUIEngine PATHS /usr/local/lib /usr/lib) SET(MYGUI_LIB_DIR ${MYGUI_LIBRARIES}) STRING(REGEX REPLACE "(.*)/.*" "\\1" MYGUI_LIB_DIR "${MYGUI_LIB_DIR}") STRING(REGEX REPLACE ".*/" "" MYGUI_LIBRARIES "${MYGUI_LIBRARIES}") @@ -109,25 +109,26 @@ ELSE (WIN32) #Unix ENDIF(MYGUI_STATIC) ENDIF (WIN32) + #Do some preparation IF (NOT WIN32) # This does not work on Windows for paths with spaces in them SEPARATE_ARGUMENTS(MYGUI_INCLUDE_DIRS) SEPARATE_ARGUMENTS(MYGUI_LIBRARIES) ENDIF (NOT WIN32) -SET(MYGUI_LIBRARIES ${MYGUI_LIBRARIES} ${FREETYPE_LIBRARIES}) +SET(MYGUI_LIBRARIES ${MYGUI_LIBRARIES} ${Freetype_LIBRARIES}) SET(MYGUI_INCLUDE_DIRS ${MYGUI_INCLUDE_DIRS} CACHE PATH "") SET(MYGUI_LIBRARIES ${MYGUI_LIBRARIES} CACHE STRING "") SET(MYGUI_LIB_DIR ${MYGUI_LIB_DIR} CACHE PATH "") IF (NOT APPLE OR NOT MYGUI_STATIC) # we need explicit freetype libs only on OS X for static build, for other cases just make it TRUE - SET(FREETYPE_LIBRARIES TRUE) + SET(Freetype_LIBRARIES TRUE) ENDIF (NOT APPLE OR NOT MYGUI_STATIC) -IF (MYGUI_INCLUDE_DIRS AND MYGUI_LIBRARIES AND FREETYPE_LIBRARIES) +IF (MYGUI_INCLUDE_DIRS AND MYGUI_LIBRARIES AND Freetype_LIBRARIES) SET(MYGUI_FOUND TRUE) -ENDIF (MYGUI_INCLUDE_DIRS AND MYGUI_LIBRARIES AND FREETYPE_LIBRARIES) +ENDIF (MYGUI_INCLUDE_DIRS AND MYGUI_LIBRARIES AND Freetype_LIBRARIES) IF (MYGUI_FOUND) MARK_AS_ADVANCED(MYGUI_LIB_DIR) @@ -151,7 +152,7 @@ ENDIF (MYGUI_FOUND) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(MyGUI DEFAULT_MSG MYGUI_INCLUDE_DIRS - FREETYPE_LIBRARIES + Freetype_LIBRARIES MYGUI_LIBRARIES) CMAKE_POLICY(POP) diff --git a/cmake/FindTinyXML.cmake b/cmake/FindTinyXML.cmake new file mode 100644 index 0000000000..d79a21c4a7 --- /dev/null +++ b/cmake/FindTinyXML.cmake @@ -0,0 +1,20 @@ +# Try to find TinyXML library + +# Once done this will define +# TinyXML_FOUND - System has the all required components. +# TinyXML_INCLUDE_DIRS - Include directory necessary for using the required components headers. +# TinyXML_LIBRARIES - Link these to use TinyXML. +# + +include(LibFindMacros) +include(PreprocessorUtils) + +libfind_pkg_detect(TinyXML tinyxml + FIND_PATH tinyxml.h + FIND_LIBRARY tinyxml +) +libfind_version_n_header(TinyXML + NAMES tinyxml.h + CONSTANTS TIXML_MAJOR_VERSION TIXML_MINOR_VERSION TIXML_PATCH_VERSION +) +libfind_process(TinyXML) diff --git a/cmake/LibFindMacros.cmake b/cmake/LibFindMacros.cmake new file mode 100644 index 0000000000..56b39a1ad9 --- /dev/null +++ b/cmake/LibFindMacros.cmake @@ -0,0 +1,363 @@ +# Version 2.2 +# Public Domain, originally written by Lasse Kärkkäinen +# Maintained at https://github.com/Tronic/cmake-modules +# Please send your improvements as pull requests on Github. + +include(CMakeParseArguments) + +# Find another package and make it a dependency of the current package. +# This also automatically forwards the "REQUIRED" argument. +# Usage: libfind_package( [extra args to find_package]) +macro (libfind_package PREFIX PKG) + set(${PREFIX}_args ${PKG} ${ARGN}) + if (${PREFIX}_FIND_REQUIRED) + set(${PREFIX}_args ${${PREFIX}_args} REQUIRED) + endif() + find_package(${${PREFIX}_args}) + set(${PREFIX}_DEPENDENCIES ${${PREFIX}_DEPENDENCIES};${PKG}) + unset(${PREFIX}_args) +endmacro() + +# A simple wrapper to make pkg-config searches a bit easier. +# Works the same as CMake's internal pkg_check_modules but is always quiet. +macro (libfind_pkg_check_modules) + find_package(PkgConfig QUIET) + if (PKG_CONFIG_FOUND) + pkg_check_modules(${ARGN} QUIET) + endif() +endmacro() + +# Avoid useless copy&pasta by doing what most simple libraries do anyway: +# pkg-config, find headers, find library. +# Usage: libfind_pkg_detect( FIND_PATH [other args] FIND_LIBRARY [other args]) +# E.g. libfind_pkg_detect(SDL2 sdl2 FIND_PATH SDL.h PATH_SUFFIXES SDL2 FIND_LIBRARY SDL2) +function (libfind_pkg_detect PREFIX) + # Parse arguments + set(argname pkgargs) + foreach (i ${ARGN}) + if ("${i}" STREQUAL "FIND_PATH") + set(argname pathargs) + elseif ("${i}" STREQUAL "FIND_LIBRARY") + set(argname libraryargs) + else() + set(${argname} ${${argname}} ${i}) + endif() + endforeach() + if (NOT pkgargs) + message(FATAL_ERROR "libfind_pkg_detect requires at least a pkg_config package name to be passed.") + endif() + # Find library + libfind_pkg_check_modules(${PREFIX}_PKGCONF ${pkgargs}) + if (pathargs) + find_path(${PREFIX}_INCLUDE_DIR NAMES ${pathargs} HINTS ${${PREFIX}_PKGCONF_INCLUDE_DIRS}) + endif() + if (libraryargs) + find_library(${PREFIX}_LIBRARY NAMES ${libraryargs} HINTS ${${PREFIX}_PKGCONF_LIBRARY_DIRS}) + endif() +endfunction() + +# libfind_header_path( [PATHS [ ...]] NAMES [name ...] VAR [QUIET]) +# Get fullpath of the first found header looking inside _INCLUDE_DIR or in the given PATHS +# Usage: libfind_header_path(Foobar NAMES foobar/version.h VAR filepath) +function (libfind_header_path PREFIX) + set(options QUIET) + set(one_value_keywords VAR PATH) + set(multi_value_keywords NAMES PATHS) + CMAKE_PARSE_ARGUMENTS(OPT "${options}" "${one_value_keywords}" "${multi_value_keywords}" ${ARGN}) + if (NOT OPT_VAR OR NOT OPT_NAMES) + message(FATAL_ERROR "Arguments VAR, NAMES are required!") + endif() + if (OPT_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Calling function with unused arguments '${OPT_UNPARSED_ARGUMENTS}'!") + endif() + if (OPT_QUIET OR ${PREFIX}_FIND_QUIETLY) + set(quiet TRUE) + endif() + set(paths ${OPT_PATHS} ${PREFIX}_INCLUDE_DIR) + + foreach(name ${OPT_NAMES}) + foreach(path ${paths}) + set(filepath "${${path}}/${name}") + # check for existance + if (EXISTS ${filepath}) + set(${OPT_VAR} ${filepath} PARENT_SCOPE) # export path + return() + endif() + endforeach() + endforeach() + + # report error if not found + set(${OPT_VAR} NOTFOUND PARENT_SCOPE) + if (NOT quiet) + message(AUTHOR_WARNING "Unable to find '${OPT_NAMES}'") + endif() +endfunction() + +# libfind_version_n_header( +# NAMES [ ...] +# DEFINES [ ...] | CONSTANTS [ ...] +# [PATHS [ ...]] +# [QUIET] +# ) +# Collect all defines|constants from a header inside _INCLUDE_DIR or in the given PATHS +# output stored to _VERSION. +# This function does nothing if the version variable is already defined. +# Usage: libfind_version_n_header(Foobar NAMES foobar/version.h DEFINES FOOBAR_VERSION_MAJOR FOOBAR_VERSION_MINOR) +function (libfind_version_n_header PREFIX) + # Skip processing if we already have a version + if (${PREFIX}_VERSION) + return() + endif() + + set(options QUIET) + set(one_value_keywords ) + set(multi_value_keywords NAMES PATHS DEFINES CONSTANTS) + CMAKE_PARSE_ARGUMENTS(OPT "${options}" "${one_value_keywords}" "${multi_value_keywords}" ${ARGN}) + if (NOT OPT_NAMES OR (NOT OPT_DEFINES AND NOT OPT_CONSTANTS)) + message(FATAL_ERROR "Arguments NAMES, DEFINES|CONSTANTS are required!") + endif() + if (OPT_DEFINES AND OPT_CONSTANTS) + message(FATAL_ERROR "Either DEFINES or CONSTANTS must be set!") + endif() + if (OPT_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Calling function with unused arguments '${OPT_UNPARSED_ARGUMENTS}'!") + endif() + if (OPT_QUIET OR ${PREFIX}_FIND_QUIETLY) + set(quiet TRUE) + set(force_quiet "QUIET") # to propagate argument QUIET + endif() + + # Read the header + libfind_header_path(${PREFIX} NAMES ${OPT_NAMES} PATHS ${OPT_PATHS} VAR filename ${force_quiet}) + if (NOT filename) + return() + endif() + file(READ "${filename}" header) + # Parse for version number + unset(version_parts) + foreach(define_name ${OPT_DEFINES}) + string(REGEX MATCH "# *define +${define_name} +((\"([^\n]*)\")|([^ \n]*))" match "${header}") + # No regex match? + if (NOT match OR match STREQUAL header) + if (NOT quiet) + message(AUTHOR_WARNING "Unable to find \#define ${define_name} \"\" from ${filename}") + endif() + return() + else() + list(APPEND version_parts "${CMAKE_MATCH_3}${CMAKE_MATCH_4}") + endif() + endforeach() + foreach(constant_name ${OPT_CONSTANTS}) + string(REGEX MATCH "${constant_name} *= *((\"([^\;]*)\")|([^ \;]*))" match "${header}") + # No regex match? + if (NOT match OR match STREQUAL header) + if (NOT quiet) + message(AUTHOR_WARNING "Unable to find ${constant_name} = \"\" from ${filename}") + endif() + return() + else() + list(APPEND version_parts "${CMAKE_MATCH_3}${CMAKE_MATCH_4}") + endif() + endforeach() + + # Export the version string + string(REPLACE ";" "." version "${version_parts}") + set(${PREFIX}_VERSION "${version}" PARENT_SCOPE) +endfunction() + +# libfind_version_header(

[PATHS [ ...]] [QUIET]) +# Extracts a version #define from a version.h file, output stored to _VERSION. +# This function does nothing if the version variable is already defined. +# Usage: libfind_version_header(Foobar foobar/version.h FOOBAR_VERSION_STR) +function (libfind_version_header PREFIX VERSION_H DEFINE_NAME) + # Skip processing if we already have a version + if (${PREFIX}_VERSION) + return() + endif() + + set(options QUIET) + set(one_value_keywords ) + set(multi_value_keywords PATHS) + CMAKE_PARSE_ARGUMENTS(OPT "${options}" "${one_value_keywords}" "${multi_value_keywords}" ${ARGN}) + if (OPT_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Calling function with unused arguments '${OPT_UNPARSED_ARGUMENTS}'!") + endif() + if (OPT_QUIET OR ${PREFIX}_FIND_QUIETLY) + set(force_quiet "QUIET") # to propagate argument QUIET + endif() + + libfind_version_n_header(${PREFIX} NAMES ${VERSION_H} PATHS ${OPT_PATHS} DEFINES ${DEFINE_NAME} ${force_quiet}) + set(${PREFIX}_VERSION "${${PREFIX}_VERSION}" PARENT_SCOPE) +endfunction() + +# Do the final processing once the paths have been detected. +# If include dirs are needed, ${PREFIX}_PROCESS_INCLUDES should be set to contain +# all the variables, each of which contain one include directory. +# Ditto for ${PREFIX}_PROCESS_LIBS and library files. +# Will set ${PREFIX}_FOUND, ${PREFIX}_INCLUDE_DIRS and ${PREFIX}_LIBRARIES. +# Also handles errors in case library detection was required, etc. +function (libfind_process PREFIX) + # Skip processing if already processed during this configuration run + if (${PREFIX}_FOUND) + return() + endif() + + set(found TRUE) # Start with the assumption that the package was found + + # Did we find any files? Did we miss includes? These are for formatting better error messages. + set(some_files FALSE) + set(missing_headers FALSE) + + # Shorthands for some variables that we need often + set(quiet ${${PREFIX}_FIND_QUIETLY}) + set(required ${${PREFIX}_FIND_REQUIRED}) + set(exactver ${${PREFIX}_FIND_VERSION_EXACT}) + set(findver "${${PREFIX}_FIND_VERSION}") + set(version "${${PREFIX}_VERSION}") + + # Lists of config option names (all, includes, libs) + unset(configopts) + set(includeopts ${${PREFIX}_PROCESS_INCLUDES}) + set(libraryopts ${${PREFIX}_PROCESS_LIBS}) + + # Process deps to add to + foreach (i ${PREFIX} ${${PREFIX}_DEPENDENCIES}) + if (DEFINED ${i}_INCLUDE_OPTS OR DEFINED ${i}_LIBRARY_OPTS) + # The package seems to export option lists that we can use, woohoo! + list(APPEND includeopts ${${i}_INCLUDE_OPTS}) + list(APPEND libraryopts ${${i}_LIBRARY_OPTS}) + else() + # If plural forms don't exist or they equal singular forms + if ((NOT DEFINED ${i}_INCLUDE_DIRS AND NOT DEFINED ${i}_LIBRARIES) OR + ({i}_INCLUDE_DIR STREQUAL ${i}_INCLUDE_DIRS AND ${i}_LIBRARY STREQUAL ${i}_LIBRARIES)) + # Singular forms can be used + if (DEFINED ${i}_INCLUDE_DIR) + list(APPEND includeopts ${i}_INCLUDE_DIR) + endif() + if (DEFINED ${i}_LIBRARY) + list(APPEND libraryopts ${i}_LIBRARY) + endif() + else() + # Oh no, we don't know the option names + message(FATAL_ERROR "We couldn't determine config variable names for ${i} includes and libs. Aieeh!") + endif() + endif() + endforeach() + + if (includeopts) + list(REMOVE_DUPLICATES includeopts) + endif() + + if (libraryopts) + list(REMOVE_DUPLICATES libraryopts) + endif() + + string(REGEX REPLACE ".*[ ;]([^ ;]*(_INCLUDE_DIRS|_LIBRARIES))" "\\1" tmp "${includeopts} ${libraryopts}") + if (NOT tmp STREQUAL "${includeopts} ${libraryopts}") + message(AUTHOR_WARNING "Plural form ${tmp} found in config options of ${PREFIX}. This works as before but is now deprecated. Please only use singular forms INCLUDE_DIR and LIBRARY, and update your find scripts for LibFindMacros > 2.0 automatic dependency system (most often you can simply remove the PROCESS variables entirely).") + endif() + + # Include/library names separated by spaces (notice: not CMake lists) + unset(includes) + unset(libs) + + # Process all includes and set found false if any are missing + foreach (i ${includeopts}) + list(APPEND configopts ${i}) + if (NOT "${${i}}" STREQUAL "${i}-NOTFOUND") + list(APPEND includes "${${i}}") + else() + set(found FALSE) + set(missing_headers TRUE) + endif() + endforeach() + + # Process all libraries and set found false if any are missing + foreach (i ${libraryopts}) + list(APPEND configopts ${i}) + if (NOT "${${i}}" STREQUAL "${i}-NOTFOUND") + list(APPEND libs "${${i}}") + else() + set (found FALSE) + endif() + endforeach() + + # Version checks + if (found AND findver) + if (NOT version) + message(WARNING "The find module for ${PREFIX} does not provide version information, so we'll just assume that it is OK. Please fix the module or remove package version requirements to get rid of this warning.") + elseif (version VERSION_LESS findver OR (exactver AND NOT version VERSION_EQUAL findver)) + set(found FALSE) + set(version_unsuitable TRUE) + endif() + endif() + + # If all-OK, hide all config options, export variables, print status and exit + if (found) + foreach (i ${configopts}) + mark_as_advanced(${i}) + endforeach() + set (${PREFIX}_INCLUDE_OPTS ${includeopts} PARENT_SCOPE) + set (${PREFIX}_LIBRARY_OPTS ${libraryopts} PARENT_SCOPE) + set (${PREFIX}_INCLUDE_DIRS ${includes} PARENT_SCOPE) + set (${PREFIX}_LIBRARIES ${libs} PARENT_SCOPE) + set (${PREFIX}_FOUND TRUE PARENT_SCOPE) + if (NOT quiet) + message(STATUS "Found ${PREFIX} ${${PREFIX}_VERSION}") + if (LIBFIND_DEBUG) + message(STATUS " ${PREFIX}_DEPENDENCIES=${${PREFIX}_DEPENDENCIES}") + message(STATUS " ${PREFIX}_INCLUDE_OPTS=${includeopts}") + message(STATUS " ${PREFIX}_INCLUDE_DIRS=${includes}") + message(STATUS " ${PREFIX}_LIBRARY_OPTS=${libraryopts}") + message(STATUS " ${PREFIX}_LIBRARIES=${libs}") + endif() + endif() + return() + endif() + + # Format messages for debug info and the type of error + set(vars "Relevant CMake configuration variables:\n") + foreach (i ${configopts}) + mark_as_advanced(CLEAR ${i}) + set(val ${${i}}) + if ("${val}" STREQUAL "${i}-NOTFOUND") + set (val "") + elseif (val AND NOT EXISTS ${val}) + set (val "${val} (does not exist)") + else() + set(some_files TRUE) + endif() + set(vars "${vars} ${i}=${val}\n") + endforeach() + set(vars "${vars}You may use CMake GUI, cmake -D or ccmake to modify the values. Delete CMakeCache.txt to discard all values and force full re-detection if necessary.\n") + if (version_unsuitable) + set(msg "${PREFIX} ${${PREFIX}_VERSION} was found but") + if (exactver) + set(msg "${msg} only version ${findver} is acceptable.") + else() + set(msg "${msg} version ${findver} is the minimum requirement.") + endif() + else() + if (missing_headers) + set(msg "We could not find development headers for ${PREFIX}. Do you have the necessary dev package installed?") + elseif (some_files) + set(msg "We only found some files of ${PREFIX}, not all of them. Perhaps your installation is incomplete or maybe we just didn't look in the right place?") + if(findver) + set(msg "${msg} This could also be caused by incompatible version (if it helps, at least ${PREFIX} ${findver} should work).") + endif() + else() + set(msg "We were unable to find package ${PREFIX}.") + endif() + endif() + + # Fatal error out if REQUIRED + if (required) + set(msg "REQUIRED PACKAGE NOT FOUND\n${msg} This package is REQUIRED and you need to install it or adjust CMake configuration in order to continue building ${CMAKE_PROJECT_NAME}.") + message(FATAL_ERROR "${msg}\n${vars}") + endif() + # Otherwise just print a nasty warning + if (NOT quiet) + message(WARNING "WARNING: MISSING PACKAGE\n${msg} This package is NOT REQUIRED and you may ignore this warning but by doing so you may miss some functionality of ${CMAKE_PROJECT_NAME}. \n${vars}") + endif() +endfunction() + diff --git a/cmake/PreprocessorUtils.cmake b/cmake/PreprocessorUtils.cmake index 38462a98d4..7fb135bd32 100644 --- a/cmake/PreprocessorUtils.cmake +++ b/cmake/PreprocessorUtils.cmake @@ -36,9 +36,9 @@ endmacro() macro(replace_preprocessor_entry VARIABLE KEYWORD NEW_VALUE) string(REGEX REPLACE "(// *)?# *define +${KEYWORD} +[^ \n]*" - "#define ${KEYWORD} ${NEW_VALUE}" - ${VARIABLE}_TEMP - ${${VARIABLE}} + "#define ${KEYWORD} ${NEW_VALUE}" + ${VARIABLE}_TEMP + ${${VARIABLE}} ) set(${VARIABLE} ${${VARIABLE}_TEMP}) endmacro() @@ -51,10 +51,35 @@ macro(set_preprocessor_entry VARIABLE KEYWORD ENABLE) endif () string(REGEX REPLACE "(// *)?# *define +${KEYWORD} *\n" - ${TMP_REPLACE_STR} - ${VARIABLE}_TEMP - ${${VARIABLE}} + ${TMP_REPLACE_STR} + ${VARIABLE}_TEMP + ${${VARIABLE}} ) set(${VARIABLE} ${${VARIABLE}_TEMP}) endmacro() - + + +# get_version_from_n_defines(result_version_name header_path [list of defines...]) +# +# get_version_from_n_defines(MyPackage_VERSION /Header/Path/HeaderName.h +# MYPACKAGE_VERSION_MAJOR +# MYPACKAGE_VERSION_MINOR +# ) +# Function call will get the values of defines MYPACKAGE_VERSION_MAJOR & MYPACKAGE_VERSION_MINOR +# from header and set "${MYPACKAGE_VERSION_MAJOR}.${MYPACKAGE_VERSION_MINOR}" into MyPackage_VERSION +# + +function(get_version_from_n_defines OUT_VAR HEADER_PATH) + if (NOT EXISTS ${HEADER_PATH}) + message(FATAL_ERROR "Unable to find '${HEADER_PATH}'") + return() + endif () + file(READ ${HEADER_PATH} _CONTENT) + unset(_DEFINES_LIST) + foreach (_DEFINE_NAME ${ARGN}) + get_preprocessor_entry(_CONTENT ${_DEFINE_NAME} _DEFINE_VALUE) + list(APPEND _DEFINES_LIST ${_DEFINE_VALUE}) + endforeach() + string(REPLACE ";" "." _VERSION "${_DEFINES_LIST}") + set(${OUT_VAR} "${_VERSION}" PARENT_SCOPE) +endfunction() diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index bbbff234c4..7a07cd59ec 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -22,7 +22,7 @@ endif (GIT_CHECKOUT) if (OPENGL_ES) find_package(OpenGLES REQUIRED) -else() +else() find_package(OpenGL REQUIRED) endif() @@ -41,13 +41,16 @@ add_component_dir (vfs ) add_component_dir (resource - scenemanager keyframemanager texturemanager resourcesystem bulletshapemanager bulletshape niffilemanager objectcache + scenemanager keyframemanager imagemanager bulletshapemanager bulletshape niffilemanager objectcache multiobjectcache resourcesystem resourcemanager + ) + +add_component_dir (shader + shadermanager shadervisitor ) add_component_dir (sceneutil - clone attach lightmanager visitor util statesetupdater controller skeleton riggeometry lightcontroller positionattitudetransform - # not used yet - #workqueue + clone attach visitor util statesetupdater controller skeleton riggeometry lightcontroller + lightmanager lightutil positionattitudetransform workqueue unrefqueue pathgridutil ) add_component_dir (nif @@ -138,6 +141,10 @@ add_component_dir (version version ) +add_component_dir (fallback + fallback validate + ) + set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui ) @@ -174,7 +181,7 @@ if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") endif() endif () -include_directories(${BULLET_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) +include_directories(${Bullet_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS} ${ESM_UI_HDR}) @@ -184,7 +191,7 @@ else() set(GL_LIB ${OPENGL_gl_LIBRARY}) endif() -target_link_libraries(components +target_link_libraries(components ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} @@ -198,7 +205,7 @@ target_link_libraries(components ${OSGGA_LIBRARIES} ${OSGFX_LIBRARIES} ${OSGANIMATION_LIBRARIES} - ${BULLET_LIBRARIES} + ${Bullet_LIBRARIES} ${SDL2_LIBRARY} # For MyGUI platform ${GL_LIB} diff --git a/components/bsa/bsa_file.hpp b/components/bsa/bsa_file.hpp index 8ed63f35da..5ff86ef650 100644 --- a/components/bsa/bsa_file.hpp +++ b/components/bsa/bsa_file.hpp @@ -90,6 +90,7 @@ private: void readHeader(); /// Get the index of a given file name, or -1 if not found + /// @note Thread safe. int getIndex(const char *str) const; public: @@ -116,12 +117,17 @@ public: /** Open a file contained in the archive. Throws an exception if the file doesn't exist. + * @note Thread safe. */ Files::IStreamPtr getFile(const char *file); + /** Open a file contained in the archive. + * @note Thread safe. + */ Files::IStreamPtr getFile(const FileStruct* file); /// Get a list of all files + /// @note Thread safe. const FileList &getList() const { return files; } }; diff --git a/components/compiler/discardparser.cpp b/components/compiler/discardparser.cpp index da114fb3dd..0e7c4718cb 100644 --- a/components/compiler/discardparser.cpp +++ b/components/compiler/discardparser.cpp @@ -14,6 +14,9 @@ namespace Compiler { if (mState==StartState || mState==CommaState || mState==MinusState) { + if (isEmpty()) + mTokenLoc = loc; + start(); return false; } @@ -25,6 +28,9 @@ namespace Compiler { if (mState==StartState || mState==CommaState || mState==MinusState) { + if (isEmpty()) + mTokenLoc = loc; + start(); return false; } @@ -37,6 +43,9 @@ namespace Compiler { if (mState==StartState || mState==CommaState) { + if (isEmpty()) + mTokenLoc = loc; + start(); return false; } @@ -48,12 +57,22 @@ namespace Compiler { if (code==Scanner::S_comma && mState==StartState) { + if (isEmpty()) + mTokenLoc = loc; + + start(); + mState = CommaState; return true; } if (code==Scanner::S_minus && (mState==StartState || mState==CommaState)) { + if (isEmpty()) + mTokenLoc = loc; + + start(); + mState = MinusState; return true; } @@ -64,6 +83,12 @@ namespace Compiler void DiscardParser::reset() { mState = StartState; + mTokenLoc = TokenLoc(); Parser::reset(); } + + const TokenLoc& DiscardParser::getTokenLoc() const + { + return mTokenLoc; + } } diff --git a/components/compiler/discardparser.hpp b/components/compiler/discardparser.hpp index bee8a87bb2..2a7ed5544d 100644 --- a/components/compiler/discardparser.hpp +++ b/components/compiler/discardparser.hpp @@ -2,6 +2,7 @@ #define COMPILER_DISCARDPARSER_H_INCLUDED #include "parser.hpp" +#include "tokenloc.hpp" namespace Compiler { @@ -14,6 +15,7 @@ namespace Compiler }; State mState; + TokenLoc mTokenLoc; public: @@ -38,8 +40,11 @@ namespace Compiler virtual void reset(); ///< Reset parser to clean state. + + /// Returns TokenLoc object for value. If no value has been parsed, the TokenLoc + /// object will be default initialised. + const TokenLoc& getTokenLoc() const; }; } #endif - diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index c0375b4366..0c8220e974 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -639,7 +639,8 @@ namespace Compiler if (code==Scanner::S_newline) { // end marker - mTokenLoc = loc; + if (mTokenLoc.mLiteral.empty()) + mTokenLoc = loc; scanner.putbackSpecial (code, loc); return false; } @@ -789,9 +790,10 @@ namespace Compiler stringParser.setOptional (true); if (*iter=='c') stringParser.smashCase(); + if (*iter=='x') stringParser.discard(); scanner.scan (stringParser); - if (optional && stringParser.isEmpty()) + if ((optional || *iter=='x') && stringParser.isEmpty()) break; if (*iter!='x') @@ -804,6 +806,9 @@ namespace Compiler if (optional) ++optionalCount; } + else + getErrorHandler().warning ("Ignoring extra argument", + stringParser.getTokenLoc()); } else if (*iter=='X') { @@ -815,6 +820,8 @@ namespace Compiler if (parser.isEmpty()) break; + else + getErrorHandler().warning("Ignoring extra argument", parser.getTokenLoc()); } else if (*iter=='z') { @@ -825,6 +832,8 @@ namespace Compiler if (discardParser.isEmpty()) break; + else + getErrorHandler().warning("Ignoring extra argument", discardParser.getTokenLoc()); } else if (*iter=='j') { @@ -870,4 +879,9 @@ namespace Compiler return optionalCount; } + + const TokenLoc& ExprParser::getTokenLoc() const + { + return mTokenLoc; + } } diff --git a/components/compiler/exprparser.hpp b/components/compiler/exprparser.hpp index 639ca65aab..dd8259ee17 100644 --- a/components/compiler/exprparser.hpp +++ b/components/compiler/exprparser.hpp @@ -103,6 +103,8 @@ namespace Compiler /// \param invert Store arguments in reverted order. /// \param ignoreKeyword A keyword that is seen as junk /// \return number of optional arguments + + const TokenLoc& getTokenLoc() const; }; } diff --git a/components/compiler/extensions.hpp b/components/compiler/extensions.hpp index 9fb9bdb95a..2adf25a57d 100644 --- a/components/compiler/extensions.hpp +++ b/components/compiler/extensions.hpp @@ -20,9 +20,9 @@ namespace Compiler l - Integer
s - Short
S - String, case preserved
- x - Optional, ignored string argument - X - Optional, ignored numeric expression - z - Optional, ignored string or numeric argument + x - Optional, ignored string argument. Emits a parser warning when this argument is supplied.
+ X - Optional, ignored numeric expression. Emits a parser warning when this argument is supplied.
+ z - Optional, ignored string or numeric argument. Emits a parser warning when this argument is supplied.
j - A piece of junk (either . or a specific keyword) **/ typedef std::string ScriptArgs; diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 8f7650191a..f67e70522f 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -59,8 +59,8 @@ namespace Compiler extensions.registerInstruction ("modfight", "l", opcodeModFight, opcodeModFightExplicit); extensions.registerInstruction ("modflee", "l", opcodeModFlee, opcodeModFleeExplicit); extensions.registerInstruction ("modalarm", "l", opcodeModAlarm, opcodeModAlarmExplicit); - extensions.registerInstruction ("toggleai", "", opcodeToggleAI, opcodeToggleAI); - extensions.registerInstruction ("tai", "", opcodeToggleAI, opcodeToggleAI); + extensions.registerInstruction ("toggleai", "", opcodeToggleAI); + extensions.registerInstruction ("tai", "", opcodeToggleAI); extensions.registerInstruction("startcombat", "c", opcodeStartCombat, opcodeStartCombatExplicit); extensions.registerInstruction("stopcombat", "x", opcodeStopCombat, opcodeStopCombatExplicit); extensions.registerFunction ("gethello", 'l', "", opcodeGetHello, opcodeGetHelloExplicit); @@ -300,6 +300,7 @@ namespace Compiler extensions.registerInstruction ("disableteleporting", "", opcodeDisableTeleporting); extensions.registerInstruction ("enableteleporting", "", opcodeEnableTeleporting); extensions.registerInstruction ("showvars", "", opcodeShowVars, opcodeShowVarsExplicit); + extensions.registerInstruction ("show", "c", opcodeShow, opcodeShowExplicit); extensions.registerInstruction ("sv", "", opcodeShowVars, opcodeShowVarsExplicit); extensions.registerInstruction("tgm", "", opcodeToggleGodMode); extensions.registerInstruction("togglegodmode", "", opcodeToggleGodMode); @@ -341,9 +342,9 @@ namespace Compiler extensions.registerInstruction ("say", "SS", opcodeSay, opcodeSayExplicit); extensions.registerFunction ("saydone", 'l', "", opcodeSayDone, opcodeSayDoneExplicit); extensions.registerInstruction ("streammusic", "S", opcodeStreamMusic); - extensions.registerInstruction ("playsound", "c", opcodePlaySound); + extensions.registerInstruction ("playsound", "cXX", opcodePlaySound); extensions.registerInstruction ("playsoundvp", "cff", opcodePlaySoundVP); - extensions.registerInstruction ("playsound3d", "c", opcodePlaySound3D, + extensions.registerInstruction ("playsound3d", "cXX", opcodePlaySound3D, opcodePlaySound3DExplicit); extensions.registerInstruction ("playsound3dvp", "cff", opcodePlaySound3DVP, opcodePlaySound3DVPExplicit); @@ -541,6 +542,7 @@ namespace Compiler extensions.registerInstruction("moveworld","cf",opcodeMoveWorld,opcodeMoveWorldExplicit); extensions.registerFunction("getstartingangle",'f',"c",opcodeGetStartingAngle,opcodeGetStartingAngleExplicit); extensions.registerInstruction("resetactors","",opcodeResetActors); + extensions.registerInstruction("fixme","",opcodeFixme, opcodeFixmeExplicit); extensions.registerInstruction("ra","",opcodeResetActors); } } diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index e7d51d934d..773ac68f1f 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -52,7 +52,6 @@ namespace Compiler const int opcodeGetLineOfSight = 0x2000222; const int opcodeGetLineOfSightExplicit = 0x2000223; const int opcodeToggleAI = 0x2000224; - const int opcodeToggleAIExplicit = 0x2000225; const int opcodeGetTarget = 0x2000238; const int opcodeGetTargetExplicit = 0x2000239; const int opcodeStartCombat = 0x200023a; @@ -277,6 +276,8 @@ namespace Compiler const int opcodeEnableTeleporting = 0x2000216; const int opcodeShowVars = 0x200021d; const int opcodeShowVarsExplicit = 0x200021e; + const int opcodeShow = 0x2000304; + const int opcodeShowExplicit = 0x2000305; const int opcodeToggleGodMode = 0x200021f; const int opcodeToggleScripts = 0x2000301; const int opcodeDisableLevitation = 0x2000220; @@ -498,6 +499,8 @@ namespace Compiler const int opcodeMoveWorld = 0x2000208; const int opcodeMoveWorldExplicit = 0x2000209; const int opcodeResetActors = 0x20002f4; + const int opcodeFixme = 0x2000302; + const int opcodeFixmeExplicit = 0x2000303; } namespace User diff --git a/components/compiler/stringparser.cpp b/components/compiler/stringparser.cpp index f8798eccd4..8041b0f024 100644 --- a/components/compiler/stringparser.cpp +++ b/components/compiler/stringparser.cpp @@ -13,7 +13,7 @@ namespace Compiler { StringParser::StringParser (ErrorHandler& errorHandler, const Context& context, Literals& literals) - : Parser (errorHandler, context), mLiterals (literals), mState (StartState), mSmashCase (false) + : Parser (errorHandler, context), mLiterals (literals), mState (StartState), mSmashCase (false), mDiscard (false) { } @@ -24,10 +24,15 @@ namespace Compiler if (mState==StartState || mState==CommaState) { start(); - if (mSmashCase) - Generator::pushString (mCode, mLiterals, Misc::StringUtils::lowerCase (name)); - else - Generator::pushString (mCode, mLiterals, name); + mTokenLoc = loc; + + if (!mDiscard) + { + if (mSmashCase) + Generator::pushString (mCode, mLiterals, Misc::StringUtils::lowerCase (name)); + else + Generator::pushString (mCode, mLiterals, name); + } return false; } @@ -75,6 +80,8 @@ namespace Compiler mState = StartState; mCode.clear(); mSmashCase = false; + mTokenLoc = TokenLoc(); + mDiscard = false; Parser::reset(); } @@ -82,4 +89,14 @@ namespace Compiler { mSmashCase = true; } + + const TokenLoc& StringParser::getTokenLoc() const + { + return mTokenLoc; + } + + void StringParser::discard() + { + mDiscard = true; + } } diff --git a/components/compiler/stringparser.hpp b/components/compiler/stringparser.hpp index 52469128fd..72dab05801 100644 --- a/components/compiler/stringparser.hpp +++ b/components/compiler/stringparser.hpp @@ -6,6 +6,7 @@ #include #include "parser.hpp" +#include "tokenloc.hpp" namespace Compiler { @@ -22,6 +23,8 @@ namespace Compiler State mState; std::vector mCode; bool mSmashCase; + TokenLoc mTokenLoc; + bool mDiscard; public: @@ -48,6 +51,14 @@ namespace Compiler void reset(); ///< Reset parser to clean state (this includes the smashCase function). + + /// Returns TokenLoc object for string. If no string has been parsed, the TokenLoc + /// object will be default initialised. + const TokenLoc& getTokenLoc() const; + + /// If parsing a string, do not add it to the literal table and do not create code + /// for it. + void discard(); }; } diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 8dc4351f6d..26f0a48069 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -110,12 +110,11 @@ Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex &index bool gamefileChecked = (file->gameFiles().count() == 0); foreach (const QString &fileName, file->gameFiles()) { - bool depFound = false; foreach (EsmFile *dependency, mFiles) { //compare filenames only. Multiple instances //of the filename (with different paths) is not relevant here. - depFound = (dependency->fileName().compare(fileName, Qt::CaseInsensitive) == 0); + bool depFound = (dependency->fileName().compare(fileName, Qt::CaseInsensitive) == 0); if (!depFound) continue; diff --git a/components/contentselector/model/loadordererror.hpp b/components/contentselector/model/loadordererror.hpp index 2b840cf69b..7067f1f22c 100644 --- a/components/contentselector/model/loadordererror.hpp +++ b/components/contentselector/model/loadordererror.hpp @@ -17,12 +17,9 @@ namespace ContentSelectorModel ErrorCode_LoadOrder = 3 }; - inline LoadOrderError() : mErrorCode(ErrorCode_None) {}; + inline LoadOrderError() : mErrorCode(ErrorCode_None) {} inline LoadOrderError(ErrorCode errorCode, QString fileName) - { - mErrorCode = errorCode; - mFileName = fileName; - } + : mErrorCode(errorCode), mFileName(fileName) {} inline ErrorCode errorCode() const { return mErrorCode; } inline QString fileName() const { return mFileName; } QString toolTip() const; diff --git a/components/esm/aisequence.cpp b/components/esm/aisequence.cpp index 5b36f9a4cd..71f87b1703 100644 --- a/components/esm/aisequence.cpp +++ b/components/esm/aisequence.cpp @@ -15,7 +15,7 @@ namespace AiSequence void AiWander::load(ESMReader &esm) { esm.getHNT (mData, "DATA"); - esm.getHNT(mStartTime, "STAR"); + esm.getHNT(mDurationData, "STAR"); // was mStartTime mStoredInitialActorPosition = false; if (esm.isNextSub("POS_")) { @@ -27,7 +27,7 @@ namespace AiSequence void AiWander::save(ESMWriter &esm) const { esm.writeHNT ("DATA", mData); - esm.writeHNT ("STAR", mStartTime); + esm.writeHNT ("STAR", mDurationData); if (mStoredInitialActorPosition) esm.writeHNT ("POS_", mInitialActorPosition); } diff --git a/components/esm/aisequence.hpp b/components/esm/aisequence.hpp index 2560fbe7df..52446d38f4 100644 --- a/components/esm/aisequence.hpp +++ b/components/esm/aisequence.hpp @@ -46,6 +46,11 @@ namespace ESM unsigned char mIdle[8]; unsigned char mShouldRepeat; }; + struct AiWanderDuration + { + float mRemainingDuration; + int unused; + }; struct AiTravelData { float mX, mY, mZ; @@ -61,7 +66,7 @@ namespace ESM struct AiWander : AiPackage { AiWanderData mData; - ESM::TimeStamp mStartTime; + AiWanderDuration mDurationData; // was ESM::TimeStamp mStartTime bool mStoredInitialActorPosition; ESM::Vector3 mInitialActorPosition; diff --git a/components/esm/cellid.cpp b/components/esm/cellid.cpp index f77e2eb551..ad91d30e04 100644 --- a/components/esm/cellid.cpp +++ b/components/esm/cellid.cpp @@ -3,6 +3,8 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +const std::string ESM::CellId::sDefaultWorldspace = "sys::default"; + void ESM::CellId::load (ESMReader &esm) { mWorldspace = esm.getHNString ("SPAC"); diff --git a/components/esm/cellid.hpp b/components/esm/cellid.hpp index 56d5a674ea..a28b717945 100644 --- a/components/esm/cellid.hpp +++ b/components/esm/cellid.hpp @@ -20,6 +20,8 @@ namespace ESM CellIndex mIndex; bool mPaged; + static const std::string sDefaultWorldspace; + void load (ESMReader &esm); void save (ESMWriter &esm) const; }; diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index 7acaed86d5..74d45bb0c3 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -1,5 +1,7 @@ #include "cellref.hpp" +#include + #include "esmreader.hpp" #include "esmwriter.hpp" @@ -43,7 +45,13 @@ void ESM::CellRef::loadId (ESMReader& esm, bool wideRefNum) mRefNum.load (esm, wideRefNum); - mRefID = esm.getHNString ("NAME"); + mRefID = esm.getHNOString ("NAME"); + if (mRefID.empty()) + { + std::ios::fmtflags f(std::cerr.flags()); + std::cerr << "Warning: got CellRef with empty RefId in " << esm.getName() << " 0x" << std::hex << esm.getFileOffset() << std::endl; + std::cerr.flags(f); + } } void ESM::CellRef::loadData(ESMReader &esm, bool &isDeleted) @@ -54,7 +62,7 @@ void ESM::CellRef::loadData(ESMReader &esm, bool &isDeleted) while (!isLoaded && esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::FourCC<'U','N','A','M'>::value: esm.getHT(mReferenceBlocked); diff --git a/components/esm/creaturestats.cpp b/components/esm/creaturestats.cpp index 9bdbf96685..c19dfcf022 100644 --- a/components/esm/creaturestats.cpp +++ b/components/esm/creaturestats.cpp @@ -20,6 +20,12 @@ void ESM::CreatureStats::load (ESMReader &esm) mDead = false; esm.getHNOT (mDead, "DEAD"); + mDeathAnimationFinished = false; + esm.getHNOT (mDeathAnimationFinished, "DFNT"); + + if (esm.getFormat() < 3 && mDead) + mDeathAnimationFinished = true; + mDied = false; esm.getHNOT (mDied, "DIED"); @@ -84,9 +90,13 @@ void ESM::CreatureStats::load (ESMReader &esm) mActorId = -1; esm.getHNOT (mActorId, "ACID"); - mDeathAnimation = 0; + mDeathAnimation = -1; esm.getHNOT (mDeathAnimation, "DANM"); + mTimeOfDeath.mDay = 0; + mTimeOfDeath.mHour = 0; + esm.getHNOT (mTimeOfDeath, "DTIM"); + mSpells.load(esm); mActiveSpells.load(esm); mAiSequence.load(esm); @@ -136,6 +146,9 @@ void ESM::CreatureStats::save (ESMWriter &esm) const if (mDead) esm.writeHNT ("DEAD", mDead); + if (mDeathAnimationFinished) + esm.writeHNT ("DFNT", mDeathAnimationFinished); + if (mDied) esm.writeHNT ("DIED", mDied); @@ -190,9 +203,12 @@ void ESM::CreatureStats::save (ESMWriter &esm) const if (mActorId != -1) esm.writeHNT ("ACID", mActorId); - if (mDeathAnimation) + if (mDeathAnimation != -1) esm.writeHNT ("DANM", mDeathAnimation); + if (mTimeOfDeath.mHour != 0 && mTimeOfDeath.mDay != 0) + esm.writeHNT ("DTIM", mTimeOfDeath); + mSpells.save(esm); mActiveSpells.save(esm); mAiSequence.save(esm); @@ -226,6 +242,7 @@ void ESM::CreatureStats::blank() mActorId = -1; mHasAiSettings = false; mDead = false; + mDeathAnimationFinished = false; mDied = false; mMurdered = false; mTalkedTo = false; @@ -240,6 +257,6 @@ void ESM::CreatureStats::blank() mFallHeight = 0.f; mRecalcDynamicStats = false; mDrawState = 0; - mDeathAnimation = 0; + mDeathAnimation = -1; mLevel = 1; } diff --git a/components/esm/creaturestats.hpp b/components/esm/creaturestats.hpp index 426e890559..55af9d5092 100644 --- a/components/esm/creaturestats.hpp +++ b/components/esm/creaturestats.hpp @@ -40,6 +40,7 @@ namespace ESM int mActorId; bool mDead; + bool mDeathAnimationFinished; bool mDied; bool mMurdered; bool mTalkedTo; @@ -56,7 +57,8 @@ namespace ESM std::string mLastHitAttemptObject; bool mRecalcDynamicStats; int mDrawState; - unsigned char mDeathAnimation; + signed char mDeathAnimation; + ESM::TimeStamp mTimeOfDeath; int mLevel; diff --git a/components/esm/debugprofile.cpp b/components/esm/debugprofile.cpp index 66e338d0c4..17249d8a3b 100644 --- a/components/esm/debugprofile.cpp +++ b/components/esm/debugprofile.cpp @@ -13,7 +13,7 @@ void ESM::DebugProfile::load (ESMReader& esm, bool &isDeleted) while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/esmcommon.hpp b/components/esm/esmcommon.hpp index d90a3444dd..d635ba6dff 100644 --- a/components/esm/esmcommon.hpp +++ b/components/esm/esmcommon.hpp @@ -15,43 +15,98 @@ enum Version VER_13 = 0x3fa66666 }; -/* A structure used for holding fixed-length strings. In the case of - LEN=4, it can be more efficient to match the string as a 32 bit - number, therefore the struct is implemented as a union with an int. - */ -template -union NAME_T + +// CRTP for FIXED_STRING class, a structure used for holding fixed-length strings +template< template class DERIVED, size_t SIZE> +class FIXED_STRING_BASE { - char name[LEN]; - uint32_t val; + /* The following methods must be implemented in derived classes: + * char const* ro_data() const; // return pointer to ro buffer + * char* rw_data(); // return pointer to rw buffer + */ +public: + enum { size = SIZE }; - bool operator==(const char *str) const - { - for(int i=0; i + bool operator==(char const (&str)[OTHER_SIZE]) const + { + size_t other_len = strnlen(str, OTHER_SIZE); + if (other_len != this->length()) + return false; + return std::strncmp(self()->ro_data(), str, size) == 0; + } + bool operator==(const char* const str) const + { + char const* const data = self()->ro_data(); + for(size_t i = 0; i < size; ++i) + { + if(data[i] != str[i]) return false; + else if(data[i] == '\0') return true; + } + return str[size] == '\0'; + } + bool operator!=(const char* const str) const { return !( (*this) == str ); } - bool operator==(const std::string &str) const - { - return (*this) == str.c_str(); - } - bool operator!=(const std::string &str) const { return !((*this)==str); } + bool operator==(const std::string& str) const + { + return (*this) == str.c_str(); + } + bool operator!=(const std::string& str) const { return !( (*this) == str ); } - bool operator==(uint32_t v) const { return v == val; } - bool operator!=(uint32_t v) const { return v != val; } + size_t data_size() const { return size; } + size_t length() const { return strnlen(self()->ro_data(), size); } + std::string toString() const { return std::string(self()->ro_data(), this->length()); } - std::string toString() const { return std::string(name, strnlen(name, LEN)); } + void assign(const std::string& value) { std::strncpy(self()->rw_data(), value.c_str(), size); } + void clear() { this->assign(""); } +private: + DERIVED const* self() const + { + return static_cast const*>(this); + } - void assign (const std::string& value) { std::strncpy (name, value.c_str(), LEN); } + // write the non-const version in terms of the const version + // Effective C++ 3rd ed., Item 3 (p. 24-25) + DERIVED* self() + { + return const_cast*>(static_cast(this)->self()); + } }; -typedef NAME_T<4> NAME; -typedef NAME_T<32> NAME32; -typedef NAME_T<64> NAME64; -typedef NAME_T<256> NAME256; +// Generic implementation +template +struct FIXED_STRING : public FIXED_STRING_BASE +{ + char data[SIZE]; + + char const* ro_data() const { return data; } + char* rw_data() { return data; } +}; + +// In the case of SIZE=4, it can be more efficient to match the string +// as a 32 bit number, therefore the struct is implemented as a union with an int. +template <> +struct FIXED_STRING<4> : public FIXED_STRING_BASE +{ + union { + char data[4]; + uint32_t intval; + }; + + using FIXED_STRING_BASE::operator==; + using FIXED_STRING_BASE::operator!=; + + bool operator==(uint32_t v) const { return v == intval; } + bool operator!=(uint32_t v) const { return v != intval; } + + char const* ro_data() const { return data; } + char* rw_data() { return data; } +}; + +typedef FIXED_STRING<4> NAME; +typedef FIXED_STRING<32> NAME32; +typedef FIXED_STRING<64> NAME64; +typedef FIXED_STRING<256> NAME256; /* This struct defines a file 'context' which can be saved and later restored by an ESMReader instance. It will save the position within diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index 6ef14a70e0..2a716427ec 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -55,8 +55,8 @@ void ESMReader::close() mCtx.leftRec = 0; mCtx.leftSub = 0; mCtx.subCached = false; - mCtx.recName.val = 0; - mCtx.subName.val = 0; + mCtx.recName.clear(); + mCtx.subName.clear(); } void ESMReader::openRaw(Files::IStreamPtr _esm, const std::string& name) @@ -204,16 +204,18 @@ void ESMReader::getSubName() } // reading the subrecord data anyway. - getExact(mCtx.subName.name, 4); - mCtx.leftRec -= 4; + const size_t subNameSize = mCtx.subName.data_size(); + getExact(mCtx.subName.rw_data(), subNameSize); + mCtx.leftRec -= subNameSize; } bool ESMReader::isEmptyOrGetName() { if (mCtx.leftRec) { - getExact(mCtx.subName.name, 4); - mCtx.leftRec -= 4; + const size_t subNameSize = mCtx.subName.data_size(); + getExact(mCtx.subName.rw_data(), subNameSize); + mCtx.leftRec -= subNameSize; return false; } return true; @@ -267,7 +269,7 @@ NAME ESMReader::getRecName() if (!hasMoreRecs()) fail("No more records, getRecName() failed"); getName(mCtx.recName); - mCtx.leftFile -= 4; + mCtx.leftFile -= mCtx.recName.data_size(); // Make sure we don't carry over any old cached subrecord // names. This can happen in some cases when we skip parts of a diff --git a/components/esm/esmwriter.cpp b/components/esm/esmwriter.cpp index 4d99991434..b8c78a2b95 100644 --- a/components/esm/esmwriter.cpp +++ b/components/esm/esmwriter.cpp @@ -9,10 +9,13 @@ namespace ESM { ESMWriter::ESMWriter() - : mStream(NULL) - , mEncoder (0) - , mRecordCount (0) - , mCounting (true) + : mRecords() + , mStream(NULL) + , mHeaderPos() + , mEncoder(NULL) + , mRecordCount(0) + , mCounting(true) + , mHeader() {} unsigned int ESMWriter::getVersion() const diff --git a/components/esm/filter.cpp b/components/esm/filter.cpp index c3658f1520..4f0519f80b 100644 --- a/components/esm/filter.cpp +++ b/components/esm/filter.cpp @@ -13,7 +13,7 @@ void ESM::Filter::load (ESMReader& esm, bool &isDeleted) while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; + uint32_t name = esm.retSubName().intval; switch (name) { case ESM::SREC_NAME: diff --git a/components/esm/loadacti.cpp b/components/esm/loadacti.cpp index b9934d3d39..ba35535b8f 100644 --- a/components/esm/loadacti.cpp +++ b/components/esm/loadacti.cpp @@ -16,7 +16,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadalch.cpp b/components/esm/loadalch.cpp index 2049045024..85c24dc2d1 100644 --- a/components/esm/loadalch.cpp +++ b/components/esm/loadalch.cpp @@ -19,7 +19,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadappa.cpp b/components/esm/loadappa.cpp index 490881fae7..1bd1f93798 100644 --- a/components/esm/loadappa.cpp +++ b/components/esm/loadappa.cpp @@ -17,7 +17,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadarmo.cpp b/components/esm/loadarmo.cpp index f2526554af..929c111a98 100644 --- a/components/esm/loadarmo.cpp +++ b/components/esm/loadarmo.cpp @@ -49,7 +49,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadbody.cpp b/components/esm/loadbody.cpp index 09113e8d1b..3e5895c2c6 100644 --- a/components/esm/loadbody.cpp +++ b/components/esm/loadbody.cpp @@ -17,7 +17,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadbook.cpp b/components/esm/loadbook.cpp index 617040be42..d5283b8eb0 100644 --- a/components/esm/loadbook.cpp +++ b/components/esm/loadbook.cpp @@ -17,7 +17,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadbsgn.cpp b/components/esm/loadbsgn.cpp index 9a4d98bb7b..e3b2f76a3a 100644 --- a/components/esm/loadbsgn.cpp +++ b/components/esm/loadbsgn.cpp @@ -18,7 +18,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index eb9d6b8d8e..7c4349d311 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -69,7 +69,7 @@ namespace ESM while (!isLoaded && esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mName = esm.getHString(); @@ -96,7 +96,7 @@ namespace ESM if (mCellId.mPaged) { - mCellId.mWorldspace = "sys::default"; + mCellId.mWorldspace = ESM::CellId::sDefaultWorldspace; mCellId.mIndex.mX = mData.mX; mCellId.mIndex.mY = mData.mY; } @@ -114,7 +114,7 @@ namespace ESM while (!isLoaded && esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::FourCC<'I','N','T','V'>::value: int waterl; diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index f92e0b5b70..549ed7309a 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -30,12 +30,12 @@ class MovedCellRef public: RefNum mRefNum; - // Target cell (if exterior) + // Coordinates of target exterior cell int mTarget[2]; - // TODO: Support moving references between exterior and interior cells! - // This may happen in saves, when an NPC follows the player. Tribunal - // introduces a henchman (which no one uses), so we may need this as well. + // The content file format does not support moving objects to an interior cell. + // The save game format does support moving to interior cells, but uses a different mechanism + // (see the MovedRefTracker implementation in MWWorld::CellStore for more details). }; /// Overloaded compare operator used to search inside a list of cell refs. @@ -141,7 +141,7 @@ struct Cell bool hasWater() const { - return (mData.mFlags&HasWater) != 0; + return ((mData.mFlags&HasWater) != 0) || isExterior(); } // Restore the given reader to the stored position. Will try to open diff --git a/components/esm/loadclas.cpp b/components/esm/loadclas.cpp index 61960b87db..1e18bc0540 100644 --- a/components/esm/loadclas.cpp +++ b/components/esm/loadclas.cpp @@ -47,7 +47,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadclot.cpp b/components/esm/loadclot.cpp index 2ef69e5e91..12f0d495d5 100644 --- a/components/esm/loadclot.cpp +++ b/components/esm/loadclot.cpp @@ -19,7 +19,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadcont.cpp b/components/esm/loadcont.cpp index 739b0d7db6..5ee785fb8d 100644 --- a/components/esm/loadcont.cpp +++ b/components/esm/loadcont.cpp @@ -36,7 +36,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadcrea.cpp b/components/esm/loadcrea.cpp index b517820896..ddd49f8df9 100644 --- a/components/esm/loadcrea.cpp +++ b/components/esm/loadcrea.cpp @@ -30,7 +30,7 @@ namespace ESM { while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loaddial.cpp b/components/esm/loaddial.cpp index bc87c4f57d..f06b42250f 100644 --- a/components/esm/loaddial.cpp +++ b/components/esm/loaddial.cpp @@ -28,7 +28,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::FourCC<'D','A','T','A'>::value: { @@ -77,14 +77,12 @@ namespace ESM void Dialogue::readInfo(ESMReader &esm, bool merge) { ESM::DialInfo info; - info.loadId(esm); - bool isDeleted = false; + info.load(esm, isDeleted); + if (!merge || mInfo.empty()) { - info.loadData(esm, isDeleted); mLookup[info.mId] = std::make_pair(mInfo.insert(mInfo.end(), info), isDeleted); - return; } @@ -96,20 +94,10 @@ namespace ESM if (lookup != mLookup.end()) { it = lookup->second.first; - - // Merge with existing record. Only the subrecords that are present in - // the new record will be overwritten. - it->loadData(esm, isDeleted); - info = *it; - - // Since the record merging may have changed the next/prev linked list connection, we need to re-insert the record + // Since the new version of this record may have changed the next/prev linked list connection, we need to re-insert the record mInfo.erase(it); mLookup.erase(lookup); } - else - { - info.loadData(esm, isDeleted); - } if (info.mNext.empty()) { diff --git a/components/esm/loaddoor.cpp b/components/esm/loaddoor.cpp index 6d8c0978c8..523d8a1efc 100644 --- a/components/esm/loaddoor.cpp +++ b/components/esm/loaddoor.cpp @@ -16,7 +16,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadench.cpp b/components/esm/loadench.cpp index ae83c63f70..78e2fb5aa3 100644 --- a/components/esm/loadench.cpp +++ b/components/esm/loadench.cpp @@ -18,7 +18,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadfact.cpp b/components/esm/loadfact.cpp index 75c482d201..07611c32fd 100644 --- a/components/esm/loadfact.cpp +++ b/components/esm/loadfact.cpp @@ -40,7 +40,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadinfo.cpp b/components/esm/loadinfo.cpp index 246baf0265..38bb163e61 100644 --- a/components/esm/loadinfo.cpp +++ b/components/esm/loadinfo.cpp @@ -9,18 +9,9 @@ namespace ESM unsigned int DialInfo::sRecordId = REC_INFO; void DialInfo::load(ESMReader &esm, bool &isDeleted) - { - loadId(esm); - loadData(esm, isDeleted); - } - - void DialInfo::loadId(ESMReader &esm) { mId = esm.getHNString("INAM"); - } - void DialInfo::loadData(ESMReader &esm, bool &isDeleted) - { isDeleted = false; mQuestStatus = QS_None; @@ -29,13 +20,10 @@ namespace ESM mPrev = esm.getHNString("PNAM"); mNext = esm.getHNString("NNAM"); - // Since there's no way to mark selects as "deleted", we have to clear the SelectStructs from all previous loadings - mSelects.clear(); - while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::FourCC<'D','A','T','A'>::value: esm.getHT(mData, 12); diff --git a/components/esm/loadinfo.hpp b/components/esm/loadinfo.hpp index 8123a9ace8..2fbc782ec9 100644 --- a/components/esm/loadinfo.hpp +++ b/components/esm/loadinfo.hpp @@ -106,11 +106,7 @@ struct DialInfo }; void load(ESMReader &esm, bool &isDeleted); - ///< Loads all sub-records of Info record - void loadId(ESMReader &esm); - ///< Loads only Id of Info record (INAM sub-record) - void loadData(ESMReader &esm, bool &isDeleted); - ///< Loads all sub-records of Info record, except INAM sub-record + ///< Loads Info record void save(ESMWriter &esm, bool isDeleted = false) const; diff --git a/components/esm/loadingr.cpp b/components/esm/loadingr.cpp index e00e73ab0b..a18e321ff7 100644 --- a/components/esm/loadingr.cpp +++ b/components/esm/loadingr.cpp @@ -17,7 +17,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index e7be033219..87d0233b5c 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -2,6 +2,8 @@ #include +#include + #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" @@ -60,7 +62,6 @@ namespace ESM , mX(0) , mY(0) , mPlugin(0) - , mEsm(NULL) , mDataTypes(0) , mDataLoaded(false) , mLandData(NULL) @@ -86,15 +87,14 @@ namespace ESM { isDeleted = false; - mEsm = &esm; - mPlugin = mEsm->getIndex(); + mPlugin = esm.getIndex(); bool hasLocation = false; bool isLoaded = false; while (!isLoaded && esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::FourCC<'I','N','T','V'>::value: esm.getSubHeaderIs(8); @@ -125,7 +125,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::FourCC<'V','N','M','L'>::value: esm.skipHSub(); @@ -180,6 +180,8 @@ namespace ESM void Land::loadData(int flags) const { + OpenThreads::ScopedLock lock(mMutex); + // Try to load only available data flags = flags & mDataTypes; // Return if all required data is loaded @@ -191,15 +193,17 @@ namespace ESM mLandData = new LandData; mLandData->mDataTypes = mDataTypes; } - mEsm->restoreContext(mContext); - if (mEsm->isNextSub("VNML")) { - condLoad(flags, DATA_VNML, mLandData->mNormals, sizeof(mLandData->mNormals)); + ESM::ESMReader reader; + reader.restoreContext(mContext); + + if (reader.isNextSub("VNML")) { + condLoad(reader, flags, DATA_VNML, mLandData->mNormals, sizeof(mLandData->mNormals)); } - if (mEsm->isNextSub("VHGT")) { + if (reader.isNextSub("VHGT")) { static VHGT vhgt; - if (condLoad(flags, DATA_VHGT, &vhgt, sizeof(vhgt))) { + if (condLoad(reader, flags, DATA_VHGT, &vhgt, sizeof(vhgt))) { float rowOffset = vhgt.mHeightOffset; for (int y = 0; y < LAND_SIZE; y++) { rowOffset += vhgt.mHeightData[y * LAND_SIZE]; @@ -217,14 +221,14 @@ namespace ESM } } - if (mEsm->isNextSub("WNAM")) { - condLoad(flags, DATA_WNAM, mLandData->mWnam, 81); + if (reader.isNextSub("WNAM")) { + condLoad(reader, flags, DATA_WNAM, mLandData->mWnam, 81); } - if (mEsm->isNextSub("VCLR")) - condLoad(flags, DATA_VCLR, mLandData->mColours, 3 * LAND_NUM_VERTS); - if (mEsm->isNextSub("VTEX")) { + if (reader.isNextSub("VCLR")) + condLoad(reader, flags, DATA_VCLR, mLandData->mColours, 3 * LAND_NUM_VERTS); + if (reader.isNextSub("VTEX")) { static uint16_t vtex[LAND_NUM_TEXTURES]; - if (condLoad(flags, DATA_VTEX, vtex, sizeof(vtex))) { + if (condLoad(reader, flags, DATA_VTEX, vtex, sizeof(vtex))) { LandData::transposeTextureData(vtex, mLandData->mTextures); } } @@ -240,25 +244,26 @@ namespace ESM } } - bool Land::condLoad(int flags, int dataFlag, void *ptr, unsigned int size) const + bool Land::condLoad(ESM::ESMReader& reader, int flags, int dataFlag, void *ptr, unsigned int size) const { if ((mDataLoaded & dataFlag) == 0 && (flags & dataFlag) != 0) { - mEsm->getHExact(ptr, size); + reader.getHExact(ptr, size); mDataLoaded |= dataFlag; return true; } - mEsm->skipHSubSize(size); + reader.skipHSubSize(size); return false; } bool Land::isDataLoaded(int flags) const { + OpenThreads::ScopedLock lock(mMutex); return (mDataLoaded & flags) == (flags & mDataTypes); } Land::Land (const Land& land) : mFlags (land.mFlags), mX (land.mX), mY (land.mY), mPlugin (land.mPlugin), - mEsm (land.mEsm), mContext (land.mContext), mDataTypes (land.mDataTypes), + mContext (land.mContext), mDataTypes (land.mDataTypes), mDataLoaded (land.mDataLoaded), mLandData (land.mLandData ? new LandData (*land.mLandData) : 0) {} @@ -275,7 +280,6 @@ namespace ESM std::swap (mX, land.mX); std::swap (mY, land.mY); std::swap (mPlugin, land.mPlugin); - std::swap (mEsm, land.mEsm); std::swap (mContext, land.mContext); std::swap (mDataTypes, land.mDataTypes); std::swap (mDataLoaded, land.mDataLoaded); diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index 65ac88cda3..a2bf1573e9 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -3,6 +3,8 @@ #include +#include + #include "esmcommon.hpp" namespace ESM @@ -31,7 +33,6 @@ struct Land // File context. This allows the ESM reader to be 'reset' to this // location later when we are ready to load the full data set. - ESMReader* mEsm; ESM_Context mContext; int mDataTypes; @@ -45,6 +46,9 @@ struct Land DATA_VTEX = 16 }; + // default height to use in case there is no Land record + static const int DEFAULT_HEIGHT = -2048; + // number of vertices per side static const int LAND_SIZE = 65; @@ -155,7 +159,9 @@ struct Land /// Loads data and marks it as loaded /// \return true if data is actually loaded from file, false otherwise /// including the case when data is already loaded - bool condLoad(int flags, int dataFlag, void *ptr, unsigned int size) const; + bool condLoad(ESM::ESMReader& reader, int flags, int dataFlag, void *ptr, unsigned int size) const; + + mutable OpenThreads::Mutex mMutex; mutable int mDataLoaded; diff --git a/components/esm/loadlevlist.cpp b/components/esm/loadlevlist.cpp index 8c0d503244..0aed15aa94 100644 --- a/components/esm/loadlevlist.cpp +++ b/components/esm/loadlevlist.cpp @@ -15,7 +15,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadligh.cpp b/components/esm/loadligh.cpp index 20c700b238..2a6dac14b0 100644 --- a/components/esm/loadligh.cpp +++ b/components/esm/loadligh.cpp @@ -17,7 +17,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadlock.cpp b/components/esm/loadlock.cpp index fc6af86990..b14353ec5a 100644 --- a/components/esm/loadlock.cpp +++ b/components/esm/loadlock.cpp @@ -17,7 +17,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadltex.cpp b/components/esm/loadltex.cpp index a42ae7c5bf..613b706e30 100644 --- a/components/esm/loadltex.cpp +++ b/components/esm/loadltex.cpp @@ -17,7 +17,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadmgef.cpp b/components/esm/loadmgef.cpp index eef58aa2f1..e33bd3ab42 100644 --- a/components/esm/loadmgef.cpp +++ b/components/esm/loadmgef.cpp @@ -211,7 +211,7 @@ void MagicEffect::load(ESMReader &esm, bool &isDeleted) while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); diff --git a/components/esm/loadmisc.cpp b/components/esm/loadmisc.cpp index 199b4e3b2c..3ba6626505 100644 --- a/components/esm/loadmisc.cpp +++ b/components/esm/loadmisc.cpp @@ -17,7 +17,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadnpc.cpp b/components/esm/loadnpc.cpp index e524e6a7cc..a68c97a6ad 100644 --- a/components/esm/loadnpc.cpp +++ b/components/esm/loadnpc.cpp @@ -26,7 +26,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadpgrd.cpp b/components/esm/loadpgrd.cpp index 69ee60eeb8..708685e727 100644 --- a/components/esm/loadpgrd.cpp +++ b/components/esm/loadpgrd.cpp @@ -46,7 +46,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mCell = esm.getHString(); @@ -127,6 +127,28 @@ namespace ESM void Pathgrid::save(ESMWriter &esm, bool isDeleted) const { + // Correct connection count and sort edges by point + // Can probably be optimized + PointList correctedPoints = mPoints; + std::vector sortedEdges; + + sortedEdges.reserve(mEdges.size()); + + for (size_t point = 0; point < correctedPoints.size(); ++point) + { + correctedPoints[point].mConnectionNum = 0; + + for (EdgeList::const_iterator it = mEdges.begin(); it != mEdges.end(); ++it) + { + if (static_cast(it->mV0) == point) + { + sortedEdges.push_back(it->mV1); + ++correctedPoints[point].mConnectionNum; + } + } + } + + // Save esm.writeHNCString("NAME", mCell); esm.writeHNT("DATA", mData, 12); @@ -136,22 +158,22 @@ namespace ESM return; } - if (!mPoints.empty()) + if (!correctedPoints.empty()) { esm.startSubRecord("PGRP"); - for (PointList::const_iterator it = mPoints.begin(); it != mPoints.end(); ++it) + for (PointList::const_iterator it = correctedPoints.begin(); it != correctedPoints.end(); ++it) { esm.writeT(*it); } esm.endRecord("PGRP"); } - if (!mEdges.empty()) + if (!sortedEdges.empty()) { esm.startSubRecord("PGRC"); - for (std::vector::const_iterator it = mEdges.begin(); it != mEdges.end(); ++it) + for (std::vector::const_iterator it = sortedEdges.begin(); it != sortedEdges.end(); ++it) { - esm.writeT(it->mV1); + esm.writeT(*it); } esm.endRecord("PGRC"); } diff --git a/components/esm/loadprob.cpp b/components/esm/loadprob.cpp index baf466490b..6307df3298 100644 --- a/components/esm/loadprob.cpp +++ b/components/esm/loadprob.cpp @@ -17,7 +17,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadrace.cpp b/components/esm/loadrace.cpp index a371751446..88ce08c913 100644 --- a/components/esm/loadrace.cpp +++ b/components/esm/loadrace.cpp @@ -29,7 +29,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadregn.cpp b/components/esm/loadregn.cpp index e5382f50b6..6e0e6db9de 100644 --- a/components/esm/loadregn.cpp +++ b/components/esm/loadregn.cpp @@ -16,7 +16,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); @@ -61,10 +61,12 @@ namespace ESM esm.getHT(mMapColor); break; case ESM::FourCC<'S','N','A','M'>::value: + { SoundRef sr; esm.getHT(sr, 33); mSoundList.push_back(sr); break; + } case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; diff --git a/components/esm/loadrepa.cpp b/components/esm/loadrepa.cpp index e4af3d9378..c04691c12f 100644 --- a/components/esm/loadrepa.cpp +++ b/components/esm/loadrepa.cpp @@ -17,7 +17,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp index b46d5b658a..1adebe45f3 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -67,15 +67,17 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::FourCC<'S','C','H','D'>::value: + { SCHD data; esm.getHT(data, 52); mData = data.mData; mId = data.mName.toString(); hasHeader = true; break; + } case ESM::FourCC<'S','C','V','R'>::value: // list of local variables loadSCVR(esm); @@ -113,7 +115,7 @@ namespace ESM memset(&data, 0, sizeof(data)); data.mData = mData; - memcpy(data.mName.name, mId.c_str(), mId.size()); + data.mName.assign(mId); esm.writeHNT("SCHD", data, 52); diff --git a/components/esm/loadskil.cpp b/components/esm/loadskil.cpp index c520897910..36204c9401 100644 --- a/components/esm/loadskil.cpp +++ b/components/esm/loadskil.cpp @@ -138,7 +138,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::FourCC<'I','N','D','X'>::value: esm.getHT(mIndex); diff --git a/components/esm/loadsndg.cpp b/components/esm/loadsndg.cpp index d84fe624d0..9bd806641b 100644 --- a/components/esm/loadsndg.cpp +++ b/components/esm/loadsndg.cpp @@ -17,7 +17,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadsoun.cpp b/components/esm/loadsoun.cpp index 82f169e54f..3b3dc1eacd 100644 --- a/components/esm/loadsoun.cpp +++ b/components/esm/loadsoun.cpp @@ -17,7 +17,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadspel.cpp b/components/esm/loadspel.cpp index 728b7bc2a7..947e6c9ec8 100644 --- a/components/esm/loadspel.cpp +++ b/components/esm/loadspel.cpp @@ -19,7 +19,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadsscr.cpp b/components/esm/loadsscr.cpp index ab4c09750d..f8854493b9 100644 --- a/components/esm/loadsscr.cpp +++ b/components/esm/loadsscr.cpp @@ -17,7 +17,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadstat.cpp b/components/esm/loadstat.cpp index 62d495ee34..6c9de22bd1 100644 --- a/components/esm/loadstat.cpp +++ b/components/esm/loadstat.cpp @@ -16,7 +16,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadtes3.cpp b/components/esm/loadtes3.cpp index df35a2579d..b16145467c 100644 --- a/components/esm/loadtes3.cpp +++ b/components/esm/loadtes3.cpp @@ -9,8 +9,8 @@ void ESM::Header::blank() { mData.version = ESM::VER_13; mData.type = 0; - mData.author.assign (""); - mData.desc.assign (""); + mData.author.clear(); + mData.desc.clear(); mData.records = 0; mFormat = CurrentFormat; mMaster.clear(); @@ -32,8 +32,8 @@ void ESM::Header::load (ESMReader &esm) esm.getSubHeader(); esm.getT(mData.version); esm.getT(mData.type); - mData.author.assign(esm.getString(sizeof(mData.author.name))); - mData.desc.assign(esm.getString(sizeof(mData.desc.name))); + mData.author.assign( esm.getString(mData.author.data_size()) ); + mData.desc.assign( esm.getString(mData.desc.data_size()) ); esm.getT(mData.records); } @@ -53,14 +53,14 @@ void ESM::Header::load (ESMReader &esm) { esm.getSubHeader(); mSCRD.resize(esm.getSubSize()); - if (mSCRD.size()) + if (!mSCRD.empty()) esm.getExact(&mSCRD[0], mSCRD.size()); } if (esm.isNextSub("SCRS")) { esm.getSubHeader(); mSCRS.resize(esm.getSubSize()); - if (mSCRS.size()) + if (!mSCRS.empty()) esm.getExact(&mSCRS[0], mSCRS.size()); } } @@ -73,8 +73,8 @@ void ESM::Header::save (ESMWriter &esm) esm.startSubRecord("HEDR"); esm.writeT(mData.version); esm.writeT(mData.type); - esm.writeFixedSizeString(mData.author.toString(), 32); - esm.writeFixedSizeString(mData.desc.toString(), 256); + esm.writeFixedSizeString(mData.author.toString(), mData.author.data_size()); + esm.writeFixedSizeString(mData.desc.toString(), mData.desc.data_size()); esm.writeT(mData.records); esm.endRecord("HEDR"); diff --git a/components/esm/loadweap.cpp b/components/esm/loadweap.cpp index 880a26bcb3..4a77ff6a00 100644 --- a/components/esm/loadweap.cpp +++ b/components/esm/loadweap.cpp @@ -17,7 +17,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().val) + switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/npcstats.cpp b/components/esm/npcstats.cpp index 10c0b6f169..4c1095b6e4 100644 --- a/components/esm/npcstats.cpp +++ b/components/esm/npcstats.cpp @@ -95,6 +95,10 @@ void ESM::NpcStats::load (ESMReader &esm) esm.getHNT (mSkillIncrease, "INCR"); + for (int i=0; i<3; ++i) + mSpecIncreases[i] = 0; + esm.getHNOT (mSpecIncreases, "SPEC"); + while (esm.isNextSub ("USED")) mUsedIds.push_back (esm.getHString()); @@ -156,6 +160,8 @@ void ESM::NpcStats::save (ESMWriter &esm) const esm.writeHNT ("INCR", mSkillIncrease); + esm.writeHNT ("SPEC", mSpecIncreases); + for (std::vector::const_iterator iter (mUsedIds.begin()); iter!=mUsedIds.end(); ++iter) esm.writeHNString ("USED", *iter); @@ -178,6 +184,8 @@ void ESM::NpcStats::blank() mLevelProgress = 0; for (int i=0; i<8; ++i) mSkillIncrease[i] = 0; + for (int i=0; i<3; ++i) + mSpecIncreases[i] = 0; mTimeToStartDrowning = 20; mCrimeId = -1; } diff --git a/components/esm/npcstats.hpp b/components/esm/npcstats.hpp index 9b27f587c7..467a099cea 100644 --- a/components/esm/npcstats.hpp +++ b/components/esm/npcstats.hpp @@ -37,6 +37,7 @@ namespace ESM int mWerewolfKills; int mLevelProgress; int mSkillIncrease[8]; + int mSpecIncreases[3]; std::vector mUsedIds; // lower case IDs float mTimeToStartDrowning; int mCrimeId; diff --git a/components/esm/objectstate.cpp b/components/esm/objectstate.cpp index d736bab66d..b80c72ffef 100644 --- a/components/esm/objectstate.cpp +++ b/components/esm/objectstate.cpp @@ -27,6 +27,9 @@ void ESM::ObjectState::load (ESMReader &esm) if (esm.isNextSub("LROT")) esm.skipHSub(); // local rotation, no longer used + mFlags = 0; + esm.getHNOT (mFlags, "FLAG"); + // obsolete int unused; esm.getHNOT(unused, "LTIM"); @@ -55,6 +58,9 @@ void ESM::ObjectState::save (ESMWriter &esm, bool inInventory) const if (!inInventory) esm.writeHNT ("POS_", mPosition, 24); + if (mFlags != 0) + esm.writeHNT ("FLAG", mFlags); + if (!mHasCustomState) esm.writeHNT ("HCUS", false); } @@ -70,6 +76,7 @@ void ESM::ObjectState::blank() mPosition.pos[i] = 0; mPosition.rot[i] = 0; } + mFlags = 0; mHasCustomState = true; } diff --git a/components/esm/objectstate.hpp b/components/esm/objectstate.hpp index 215cb74baa..5b78074af3 100644 --- a/components/esm/objectstate.hpp +++ b/components/esm/objectstate.hpp @@ -24,6 +24,7 @@ namespace ESM unsigned char mEnabled; int mCount; ESM::Position mPosition; + unsigned int mFlags; // Is there any class-specific state following the ObjectState bool mHasCustomState; diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp index cf9f68c9a8..3220f496e0 100644 --- a/components/esm/savedgame.cpp +++ b/components/esm/savedgame.cpp @@ -5,7 +5,7 @@ #include "defs.hpp" unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; -int ESM::SavedGame::sCurrentFormat = 2; +int ESM::SavedGame::sCurrentFormat = 3; void ESM::SavedGame::load (ESMReader &esm) { diff --git a/components/esm/spellstate.cpp b/components/esm/spellstate.cpp index 3ed3329b46..a21078e105 100644 --- a/components/esm/spellstate.cpp +++ b/components/esm/spellstate.cpp @@ -12,7 +12,7 @@ namespace ESM { std::string id = esm.getHString(); - std::map random; + SpellParams state; while (esm.isNextSub("INDX")) { int index; @@ -21,10 +21,16 @@ namespace ESM float magnitude; esm.getHNT(magnitude, "RAND"); - random[index] = magnitude; + state.mEffectRands[index] = magnitude; } - mSpells[id] = random; + while (esm.isNextSub("PURG")) { + int index; + esm.getHT(index); + state.mPurgedEffects.insert(index); + } + + mSpells[id] = state; } while (esm.isNextSub("PERM")) @@ -73,12 +79,16 @@ namespace ESM { esm.writeHNString("SPEL", it->first); - const std::map& random = it->second; - for (std::map::const_iterator rIt = random.begin(); rIt != random.end(); ++rIt) + const std::map& random = it->second.mEffectRands; + for (std::map::const_iterator rIt = random.begin(); rIt != random.end(); ++rIt) { esm.writeHNT("INDX", rIt->first); esm.writeHNT("RAND", rIt->second); } + + const std::set& purges = it->second.mPurgedEffects; + for (std::set::const_iterator pIt = purges.begin(); pIt != purges.end(); ++pIt) + esm.writeHNT("PURG", *pIt); } for (std::map >::const_iterator it = mPermanentSpellEffects.begin(); it != mPermanentSpellEffects.end(); ++it) diff --git a/components/esm/spellstate.hpp b/components/esm/spellstate.hpp index 028e6a3878..ec613afabe 100644 --- a/components/esm/spellstate.hpp +++ b/components/esm/spellstate.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "defs.hpp" @@ -28,7 +29,11 @@ namespace ESM float mMagnitude; }; - typedef std::map > TContainer; + struct SpellParams { + std::map mEffectRands; + std::set mPurgedEffects; + }; + typedef std::map TContainer; TContainer mSpells; std::map > mPermanentSpellEffects; diff --git a/components/esm/transport.cpp b/components/esm/transport.cpp index da0a5f7676..063c033299 100644 --- a/components/esm/transport.cpp +++ b/components/esm/transport.cpp @@ -8,13 +8,13 @@ namespace ESM void Transport::add(ESMReader &esm) { - if (esm.retSubName().val == ESM::FourCC<'D','O','D','T'>::value) + if (esm.retSubName().intval == ESM::FourCC<'D','O','D','T'>::value) { Dest dodt; esm.getHExact(&dodt.mPos, 24); mList.push_back(dodt); } - else if (esm.retSubName().val == ESM::FourCC<'D','N','A','M'>::value) + else if (esm.retSubName().intval == ESM::FourCC<'D','N','A','M'>::value) { mList.back().mCellName = esm.getHString(); } diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index f0865a0a98..8a592bfc8c 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include #include @@ -14,8 +16,15 @@ namespace ESMTerrain { - Storage::Storage(const VFS::Manager *vfs) + const float defaultHeight = ESM::Land::DEFAULT_HEIGHT; + + Storage::Storage(const VFS::Manager *vfs, const std::string& normalMapPattern, const std::string& normalHeightMapPattern, bool autoUseNormalMaps, const std::string& specularMapPattern, bool autoUseSpecularMaps) : mVFS(vfs) + , mNormalMapPattern(normalMapPattern) + , mNormalHeightMapPattern(normalHeightMapPattern) + , mAutoUseNormalMaps(autoUseNormalMaps) + , mSpecularMapPattern(specularMapPattern) + , mAutoUseSpecularMaps(autoUseSpecularMaps) { } @@ -62,7 +71,9 @@ namespace ESMTerrain return true; } - return false; + min = defaultHeight; + max = defaultHeight; + return true; } void Storage::fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row) @@ -203,7 +214,7 @@ namespace ESMTerrain assert (vertX < numVerts); assert (vertY < numVerts); - float height = -2048; + float height = defaultHeight; if (heightData) height = heightData->mHeights[col*ESM::Land::LAND_SIZE + row]; @@ -395,9 +406,9 @@ namespace ESMTerrain int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0; if (blendIndex == i) - pData[y*blendmapSize*channels + x*channels + channel] = 255; + pData[(blendmapSize - y - 1)*blendmapSize*channels + x*channels + channel] = 255; else - pData[y*blendmapSize*channels + x*channels + channel] = 0; + pData[(blendmapSize - y - 1)*blendmapSize*channels + x*channels + channel] = 0; } } @@ -412,7 +423,7 @@ namespace ESMTerrain const ESM::Land* land = getLand(cellX, cellY); if (!land || !(land->mDataTypes&ESM::Land::DATA_VHGT)) - return -2048; + return defaultHeight; // Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition @@ -494,6 +505,8 @@ namespace ESMTerrain Terrain::LayerInfo Storage::getLayerInfo(const std::string& texture) { + OpenThreads::ScopedLock lock(mLayerInfoMutex); + // Already have this cached? std::map::iterator found = mLayerInfoMap.find(texture); if (found != mLayerInfoMap.end()) @@ -503,28 +516,34 @@ namespace ESMTerrain info.mParallax = false; info.mSpecular = false; info.mDiffuseMap = texture; - std::string texture_ = texture; - boost::replace_last(texture_, ".", "_nh."); - if (mVFS->exists(texture_)) + if (mAutoUseNormalMaps) { - info.mNormalMap = texture_; - info.mParallax = true; - } - else - { - texture_ = texture; - boost::replace_last(texture_, ".", "_n."); + std::string texture_ = texture; + boost::replace_last(texture_, ".", mNormalHeightMapPattern + "."); if (mVFS->exists(texture_)) + { info.mNormalMap = texture_; + info.mParallax = true; + } + else + { + texture_ = texture; + boost::replace_last(texture_, ".", mNormalMapPattern + "."); + if (mVFS->exists(texture_)) + info.mNormalMap = texture_; + } } - texture_ = texture; - boost::replace_last(texture_, ".", "_diffusespec."); - if (mVFS->exists(texture_)) + if (mAutoUseSpecularMaps) { - info.mDiffuseMap = texture_; - info.mSpecular = true; + std::string texture_ = texture; + boost::replace_last(texture_, ".", mSpecularMapPattern + "."); + if (mVFS->exists(texture_)) + { + info.mDiffuseMap = texture_; + info.mSpecular = true; + } } mLayerInfoMap[texture] = info; diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp index 5b8ca953d4..092998b2e7 100644 --- a/components/esmterrain/storage.hpp +++ b/components/esmterrain/storage.hpp @@ -1,6 +1,8 @@ #ifndef COMPONENTS_ESM_TERRAIN_STORAGE_H #define COMPONENTS_ESM_TERRAIN_STORAGE_H +#include + #include #include @@ -25,7 +27,7 @@ namespace ESMTerrain virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0; public: - Storage(const VFS::Manager* vfs); + Storage(const VFS::Manager* vfs, const std::string& normalMapPattern = "", const std::string& normalHeightMapPattern = "", bool autoUseNormalMaps = false, const std::string& specularMapPattern = "", bool autoUseSpecularMaps = false); /// Data is loaded first, if necessary. Will return a 0-pointer if there is no data for /// any of the data types specified via \a flags. Will also return a 0-pointer if there @@ -105,6 +107,14 @@ namespace ESMTerrain std::string getTextureName (UniqueTextureId id); std::map mLayerInfoMap; + OpenThreads::Mutex mLayerInfoMutex; + + std::string mNormalMapPattern; + std::string mNormalHeightMapPattern; + bool mAutoUseNormalMaps; + + std::string mSpecularMapPattern; + bool mAutoUseSpecularMaps; Terrain::LayerInfo getLayerInfo(const std::string& texture); }; diff --git a/apps/openmw/mwworld/fallback.cpp b/components/fallback/fallback.cpp similarity index 75% rename from apps/openmw/mwworld/fallback.cpp rename to components/fallback/fallback.cpp index e810f8241b..3545956606 100644 --- a/apps/openmw/mwworld/fallback.cpp +++ b/components/fallback/fallback.cpp @@ -2,12 +2,12 @@ #include -namespace MWWorld +namespace Fallback { - Fallback::Fallback(const std::map& fallback):mFallbackMap(fallback) + Map::Map(const std::map& fallback):mFallbackMap(fallback) {} - std::string Fallback::getFallbackString(const std::string& fall) const + std::string Map::getFallbackString(const std::string& fall) const { std::map::const_iterator it; if((it = mFallbackMap.find(fall)) == mFallbackMap.end()) @@ -16,7 +16,7 @@ namespace MWWorld } return it->second; } - float Fallback::getFallbackFloat(const std::string& fall) const + float Map::getFallbackFloat(const std::string& fall) const { std::string fallback=getFallbackString(fall); if(fallback.empty()) @@ -24,7 +24,7 @@ namespace MWWorld else return boost::lexical_cast(fallback); } - int Fallback::getFallbackInt(const std::string& fall) const + int Map::getFallbackInt(const std::string& fall) const { std::string fallback=getFallbackString(fall); if(fallback.empty()) @@ -33,7 +33,7 @@ namespace MWWorld return boost::lexical_cast(fallback); } - bool Fallback::getFallbackBool(const std::string& fall) const + bool Map::getFallbackBool(const std::string& fall) const { std::string fallback=getFallbackString(fall); if(fallback.empty()) @@ -41,7 +41,7 @@ namespace MWWorld else return boost::lexical_cast(fallback); } - osg::Vec4f Fallback::getFallbackColour(const std::string& fall) const + osg::Vec4f Map::getFallbackColour(const std::string& fall) const { std::string sum=getFallbackString(fall); if(sum.empty()) diff --git a/apps/openmw/mwworld/fallback.hpp b/components/fallback/fallback.hpp similarity index 57% rename from apps/openmw/mwworld/fallback.hpp rename to components/fallback/fallback.hpp index af47063ee8..e649365317 100644 --- a/apps/openmw/mwworld/fallback.hpp +++ b/components/fallback/fallback.hpp @@ -1,18 +1,21 @@ -#ifndef GAME_MWWORLD_FALLBACK_H -#define GAME_MWWORLD_FALLBACK_H +#ifndef OPENMW_COMPONENTS_FALLBACK_H +#define OPENMW_COMPONENTS_FALLBACK_H #include #include #include -namespace MWWorld +namespace Fallback { - class Fallback + /// @brief contains settings imported from the Morrowind INI file. + class Map { - const std::map mFallbackMap; + std::map mFallbackMap; public: - Fallback(const std::map& fallback); + Map(const std::map& fallback); + Map() {} + std::string getFallbackString(const std::string& fall) const; float getFallbackFloat(const std::string& fall) const; int getFallbackInt(const std::string& fall) const; diff --git a/components/fallback/validate.hpp b/components/fallback/validate.hpp new file mode 100644 index 0000000000..3b6398d6a2 --- /dev/null +++ b/components/fallback/validate.hpp @@ -0,0 +1,48 @@ +#ifndef OPENMW_COMPONENTS_FALLBACK_VALIDATE_H +#define OPENMW_COMPONENTS_FALLBACK_VALIDATE_H + +#include + +// Parses and validates a fallback map from boost program_options. +// Note: for boost to pick up the validate function, you need to pull in the namespace e.g. +// by using namespace Fallback; + +namespace Fallback +{ + + struct FallbackMap { + std::map mMap; + }; + + void validate(boost::any &v, std::vector const &tokens, FallbackMap*, int) + { + if(v.empty()) + { + v = boost::any(FallbackMap()); + } + + FallbackMap *map = boost::any_cast(&v); + + for(std::vector::const_iterator it=tokens.begin(); it != tokens.end(); ++it) + { + int sep = it->find(","); + if(sep < 1 || sep == (int)it->length()-1) + #if (BOOST_VERSION < 104200) + throw boost::program_options::validation_error("invalid value"); + #else + throw boost::program_options::validation_error(boost::program_options::validation_error::invalid_option_value); + #endif + + std::string key(it->substr(0,sep)); + std::string value(it->substr(sep+1)); + + if(map->mMap.find(key) == map->mMap.end()) + { + map->mMap.insert(std::make_pair (key,value)); + } + } + } + +} + +#endif diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 9c9cebe96f..5ac3dd695f 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -22,7 +22,6 @@ static const char* const applicationName = "OpenMW"; static const char* const applicationName = "openmw"; #endif -const char* const mwToken = "?mw?"; const char* const localToken = "?local?"; const char* const userDataToken = "?userdata?"; const char* const globalToken = "?global?"; @@ -45,7 +44,6 @@ ConfigurationManager::~ConfigurationManager() void ConfigurationManager::setupTokensMapping() { - mTokensMapping.insert(std::make_pair(mwToken, &FixedPath<>::getInstallPath)); mTokensMapping.insert(std::make_pair(localToken, &FixedPath<>::getLocalPath)); mTokensMapping.insert(std::make_pair(userDataToken, &FixedPath<>::getUserDataPath)); mTokensMapping.insert(std::make_pair(globalToken, &FixedPath<>::getGlobalDataPath)); diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 58ee5c1aef..c05dbbb458 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -1,13 +1,7 @@ #ifndef COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP #define COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP -#if defined(_WIN32) && !defined(__MINGW32__) -#include -#elif defined HAVE_UNORDERED_MAP -#include -#else -#include -#endif +#include #include @@ -52,11 +46,7 @@ struct ConfigurationManager typedef Files::FixedPath<> FixedPathType; typedef const boost::filesystem::path& (FixedPathType::*path_type_f)() const; - #if defined HAVE_UNORDERED_MAP - typedef std::unordered_map TokensMappingContainer; - #else - typedef std::tr1::unordered_map TokensMappingContainer; - #endif + typedef std::map TokensMappingContainer; bool loadConfig(const boost::filesystem::path& path, boost::program_options::variables_map& variables, diff --git a/components/interpreter/defines.cpp b/components/interpreter/defines.cpp index 2ceb857c40..fafaf1feff 100644 --- a/components/interpreter/defines.cpp +++ b/components/interpreter/defines.cpp @@ -26,13 +26,14 @@ namespace Interpreter{ return a.length() > b.length(); } - std::string fixDefinesReal(std::string text, char eschar, bool isBook, Context& context) + std::string fixDefinesReal(std::string text, bool dialogue, Context& context) { unsigned int start = 0; std::ostringstream retval; for(unsigned int i = 0; i < text.length(); i++) { - if(text[i] == eschar) + char eschar = text[i]; + if(eschar == '%' || eschar == '^') { retval << text.substr(start, i - start); std::string temp = Misc::StringUtils::lowerCase(text.substr(i+1, 100)); @@ -113,7 +114,7 @@ namespace Interpreter{ retval << context.getCurrentCellName(); } - else if(eschar == '%' && !isBook) { // In Dialogue, not messagebox + else if(dialogue) { // In Dialogue, not messagebox if( (found = check(temp, "faction", &i, &start))){ retval << context.getNPCFaction(); } @@ -207,15 +208,15 @@ namespace Interpreter{ return retval.str (); } - std::string fixDefinesDialog(std::string text, Context& context){ - return fixDefinesReal(text, '%', false, context); + std::string fixDefinesDialog(const std::string& text, Context& context){ + return fixDefinesReal(text, true, context); } - std::string fixDefinesMsgBox(std::string text, Context& context){ - return fixDefinesReal(text, '^', false, context); + std::string fixDefinesMsgBox(const std::string& text, Context& context){ + return fixDefinesReal(text, false, context); } - std::string fixDefinesBook(std::string text, Context& context){ - return fixDefinesReal(text, '%', true, context); + std::string fixDefinesBook(const std::string& text, Context& context){ + return fixDefinesReal(text, false, context); } } diff --git a/components/interpreter/defines.hpp b/components/interpreter/defines.hpp index 00c4386b88..3471b20305 100644 --- a/components/interpreter/defines.hpp +++ b/components/interpreter/defines.hpp @@ -5,9 +5,9 @@ #include "context.hpp" namespace Interpreter{ - std::string fixDefinesDialog(std::string text, Context& context); - std::string fixDefinesMsgBox(std::string text, Context& context); - std::string fixDefinesBook(std::string text, Context& context); + std::string fixDefinesDialog(const std::string& text, Context& context); + std::string fixDefinesMsgBox(const std::string& text, Context& context); + std::string fixDefinesBook(const std::string& text, Context& context); } #endif diff --git a/components/myguiplatform/myguiplatform.cpp b/components/myguiplatform/myguiplatform.cpp index 22b88438f6..dfb2e6539d 100644 --- a/components/myguiplatform/myguiplatform.cpp +++ b/components/myguiplatform/myguiplatform.cpp @@ -7,14 +7,14 @@ namespace osgMyGUI { -Platform::Platform(osgViewer::Viewer *viewer, osg::Group *guiRoot, Resource::TextureManager *textureManager, float uiScalingFactor) +Platform::Platform(osgViewer::Viewer *viewer, osg::Group *guiRoot, Resource::ImageManager *imageManager, float uiScalingFactor) : mRenderManager(nullptr) , mDataManager(nullptr) , mLogManager(nullptr) , mLogFacility(nullptr) { mLogManager = new MyGUI::LogManager(); - mRenderManager = new RenderManager(viewer, guiRoot, textureManager, uiScalingFactor); + mRenderManager = new RenderManager(viewer, guiRoot, imageManager, uiScalingFactor); mDataManager = new DataManager(); } diff --git a/components/myguiplatform/myguiplatform.hpp b/components/myguiplatform/myguiplatform.hpp index 56562e12a6..5ffbe0be70 100644 --- a/components/myguiplatform/myguiplatform.hpp +++ b/components/myguiplatform/myguiplatform.hpp @@ -13,7 +13,7 @@ namespace osg } namespace Resource { - class TextureManager; + class ImageManager; } namespace MyGUI { @@ -30,7 +30,7 @@ namespace osgMyGUI class Platform { public: - Platform(osgViewer::Viewer* viewer, osg::Group* guiRoot, Resource::TextureManager* textureManager, float uiScalingFactor); + Platform(osgViewer::Viewer* viewer, osg::Group* guiRoot, Resource::ImageManager* imageManager, float uiScalingFactor); ~Platform(); @@ -47,6 +47,9 @@ namespace osgMyGUI DataManager* mDataManager; MyGUI::LogManager* mLogManager; LogFacility* mLogFacility; + + void operator=(const Platform&); + Platform(const Platform&); }; } diff --git a/components/myguiplatform/myguirendermanager.cpp b/components/myguiplatform/myguirendermanager.cpp index 5bd56dc8f4..7654a28215 100644 --- a/components/myguiplatform/myguirendermanager.cpp +++ b/components/myguiplatform/myguirendermanager.cpp @@ -6,15 +6,15 @@ #include #include -#include #include #include +#include #include #include -#include +#include #include "myguitexture.hpp" @@ -23,7 +23,6 @@ #define MYGUI_PLATFORM_EXCEPT(dest) do { \ MYGUI_PLATFORM_LOG(Critical, dest); \ - MYGUI_DBG_BREAK;\ std::ostringstream stream; \ stream << dest << "\n"; \ MYGUI_BASE_EXCEPT(stream.str().c_str(), "MyGUI"); \ @@ -33,7 +32,6 @@ if ( ! (exp) ) \ { \ MYGUI_PLATFORM_LOG(Critical, dest); \ - MYGUI_DBG_BREAK;\ std::ostringstream stream; \ stream << dest << "\n"; \ MYGUI_BASE_EXCEPT(stream.str().c_str(), "MyGUI"); \ @@ -131,9 +129,8 @@ public: if(texture) state->applyTextureAttribute(0, texture); - // VBOs disabled due to crash in OSG: http://forum.openscenegraph.org/viewtopic.php?t=14909 - osg::GLBufferObject* bufferobject = 0;//state->isVertexBufferObjectSupported() ? vbo->getOrCreateGLBufferObject(state->getContextID()) : 0; - if (0)//bufferobject) + osg::GLBufferObject* bufferobject = state->isVertexBufferObjectSupported() ? vbo->getOrCreateGLBufferObject(state->getContextID()) : 0; + if (bufferobject) { state->bindVertexBufferObject(bufferobject); @@ -189,6 +186,12 @@ public: mStateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON); mStateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); mStateSet->setMode(GL_BLEND, osg::StateAttribute::ON); + + // need to flip tex coords since MyGUI uses DirectX convention of top left image origin + osg::Matrix flipMat; + flipMat.preMultTranslate(osg::Vec3f(0,1,0)); + flipMat.preMultScale(osg::Vec3f(1,-1,1)); + mStateSet->setTextureAttribute(0, new osg::TexMat(flipMat), osg::StateAttribute::ON); } Drawable(const Drawable ©, const osg::CopyOp ©op=osg::CopyOp::SHALLOW_COPY) : osg::Drawable(copy, copyop) @@ -346,10 +349,10 @@ void OSGVertexBuffer::create() // --------------------------------------------------------------------------- -RenderManager::RenderManager(osgViewer::Viewer *viewer, osg::Group *sceneroot, Resource::TextureManager* textureManager, float scalingFactor) +RenderManager::RenderManager(osgViewer::Viewer *viewer, osg::Group *sceneroot, Resource::ImageManager* imageManager, float scalingFactor) : mViewer(viewer) , mSceneRoot(sceneroot) - , mTextureManager(textureManager) + , mImageManager(imageManager) , mUpdate(false) , mIsInitialise(false) , mInvScalingFactor(1.f) @@ -387,9 +390,6 @@ void RenderManager::initialise() mDrawable = new Drawable(this); - osg::ref_ptr geode = new osg::Geode; - geode->addDrawable(mDrawable.get()); - osg::ref_ptr camera = new osg::Camera(); camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); camera->setProjectionResizePolicy(osg::Camera::FIXED); @@ -397,8 +397,8 @@ void RenderManager::initialise() camera->setViewMatrix(osg::Matrix::identity()); camera->setRenderOrder(osg::Camera::POST_RENDER); camera->setClearMask(GL_NONE); - geode->setCullingActive(false); - camera->addChild(geode.get()); + mDrawable->setCullingActive(false); + camera->addChild(mDrawable.get()); mGuiRoot = camera; mSceneRoot->addChild(mGuiRoot.get()); @@ -516,7 +516,7 @@ MyGUI::ITexture* RenderManager::createTexture(const std::string &name) mTextures.erase(item); } - OSGTexture* texture = new OSGTexture(name, mTextureManager); + OSGTexture* texture = new OSGTexture(name, mImageManager); mTextures.insert(std::make_pair(name, texture)); return texture; } diff --git a/components/myguiplatform/myguirendermanager.hpp b/components/myguiplatform/myguirendermanager.hpp index f2251cdb04..4a0aae3cda 100644 --- a/components/myguiplatform/myguirendermanager.hpp +++ b/components/myguiplatform/myguirendermanager.hpp @@ -7,7 +7,7 @@ namespace Resource { - class TextureManager; + class ImageManager; } namespace osgViewer @@ -33,7 +33,7 @@ class RenderManager : public MyGUI::RenderManager, public MyGUI::IRenderTarget osg::ref_ptr mViewer; osg::ref_ptr mSceneRoot; osg::ref_ptr mDrawable; - Resource::TextureManager* mTextureManager; + Resource::ImageManager* mImageManager; MyGUI::IntSize mViewSize; bool mUpdate; @@ -54,7 +54,7 @@ class RenderManager : public MyGUI::RenderManager, public MyGUI::IRenderTarget void destroyAllResources(); public: - RenderManager(osgViewer::Viewer *viewer, osg::Group *sceneroot, Resource::TextureManager* textureManager, float scalingFactor); + RenderManager(osgViewer::Viewer *viewer, osg::Group *sceneroot, Resource::ImageManager* imageManager, float scalingFactor); virtual ~RenderManager(); void initialise(); diff --git a/components/myguiplatform/myguitexture.cpp b/components/myguiplatform/myguitexture.cpp index 50ac5c1f34..85ff3e3b5a 100644 --- a/components/myguiplatform/myguitexture.cpp +++ b/components/myguiplatform/myguitexture.cpp @@ -5,14 +5,14 @@ #include -#include +#include namespace osgMyGUI { - OSGTexture::OSGTexture(const std::string &name, Resource::TextureManager* textureManager) + OSGTexture::OSGTexture(const std::string &name, Resource::ImageManager* imageManager) : mName(name) - , mTextureManager(textureManager) + , mImageManager(imageManager) , mFormat(MyGUI::PixelFormat::Unknow) , mUsage(MyGUI::TextureUsage::Default) , mNumElemBytes(0) @@ -20,7 +20,7 @@ namespace osgMyGUI } OSGTexture::OSGTexture(osg::Texture2D *texture) - : mTextureManager(NULL) + : mImageManager(NULL) , mTexture(texture) , mFormat(MyGUI::PixelFormat::Unknow) , mUsage(MyGUI::TextureUsage::Default) @@ -83,10 +83,15 @@ namespace osgMyGUI void OSGTexture::loadFromFile(const std::string &fname) { - if (!mTextureManager) - throw std::runtime_error("No texturemanager set"); + if (!mImageManager) + throw std::runtime_error("No imagemanager set"); - mTexture = mTextureManager->getTexture2D(fname, osg::Texture2D::CLAMP_TO_EDGE, osg::Texture2D::CLAMP_TO_EDGE); + osg::ref_ptr image (mImageManager->getImage(fname)); + mTexture = new osg::Texture2D(image); + mTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + mTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + mTexture->setTextureWidth(image->s()); + mTexture->setTextureHeight(image->t()); // disable mip-maps mTexture->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::LINEAR); @@ -105,8 +110,6 @@ namespace osgMyGUI { if(!mTexture.valid()) return 0; - osg::Image *image = mTexture->getImage(); - if(image) return image->s(); return mTexture->getTextureWidth(); } @@ -114,8 +117,6 @@ namespace osgMyGUI { if(!mTexture.valid()) return 0; - osg::Image *image = mTexture->getImage(); - if(image) return image->t(); return mTexture->getTextureHeight(); } @@ -126,15 +127,12 @@ namespace osgMyGUI if (mLockedImage.valid()) throw std::runtime_error("Texture already locked"); - mLockedImage = mTexture->getImage(); - if(!mLockedImage.valid()) - { - mLockedImage = new osg::Image(); - mLockedImage->allocateImage( - mTexture->getTextureWidth(), mTexture->getTextureHeight(), mTexture->getTextureDepth(), - mTexture->getSourceFormat(), mTexture->getSourceType() - ); - } + mLockedImage = new osg::Image(); + mLockedImage->allocateImage( + mTexture->getTextureWidth(), mTexture->getTextureHeight(), mTexture->getTextureDepth(), + mTexture->getSourceFormat(), mTexture->getSourceType() + ); + return mLockedImage->data(); } @@ -143,6 +141,8 @@ namespace osgMyGUI if (!mLockedImage.valid()) throw std::runtime_error("Texture not locked"); + mLockedImage->flipVertical(); + // mTexture might be in use by the draw thread, so create a new texture instead and use that. osg::ref_ptr newTexture = new osg::Texture2D; newTexture->setTextureSize(getWidth(), getHeight()); diff --git a/components/myguiplatform/myguitexture.hpp b/components/myguiplatform/myguitexture.hpp index de385e94d6..101e2135bf 100644 --- a/components/myguiplatform/myguitexture.hpp +++ b/components/myguiplatform/myguitexture.hpp @@ -13,7 +13,7 @@ namespace osg namespace Resource { - class TextureManager; + class ImageManager; } namespace osgMyGUI @@ -21,7 +21,7 @@ namespace osgMyGUI class OSGTexture : public MyGUI::ITexture { std::string mName; - Resource::TextureManager* mTextureManager; + Resource::ImageManager* mImageManager; osg::ref_ptr mLockedImage; osg::ref_ptr mTexture; @@ -30,7 +30,7 @@ namespace osgMyGUI size_t mNumElemBytes; public: - OSGTexture(const std::string &name, Resource::TextureManager* textureManager); + OSGTexture(const std::string &name, Resource::ImageManager* imageManager); OSGTexture(osg::Texture2D* texture); virtual ~OSGTexture(); diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 5a60ab8a5e..1de7d0966c 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -65,8 +65,14 @@ void ShapeData::read(NIFStream *nif) uvlist.resize(uvs); for(int i = 0;i < uvs;i++) { - uvlist[i] = new osg::Vec2Array(osg::Array::BIND_PER_VERTEX); - nif->getVector2s(uvlist[i], verts); + osg::Vec2Array* list = uvlist[i] = new osg::Vec2Array(osg::Array::BIND_PER_VERTEX); + nif->getVector2s(list, verts); + + // flip the texture coordinates to convert them to the OpenGL convention of bottom-left image origin + for (unsigned int uv=0; uvsize(); ++uv) + { + (*list)[uv] = osg::Vec2((*list)[uv].x(), 1.f - (*list)[uv].y()); + } } } } @@ -148,7 +154,7 @@ void NiFloatData::read(NIFStream *nif) void NiPixelData::read(NIFStream *nif) { - nif->getInt(); // always 0 or 1 + fmt = (Format)nif->getUInt(); rmask = nif->getInt(); // usually 0xff gmask = nif->getInt(); // usually 0xff00 @@ -163,19 +169,23 @@ void NiPixelData::read(NIFStream *nif) mips = nif->getInt(); // Bytes per pixel, should be bpp * 8 - /*int bytes =*/ nif->getInt(); + /* int bytes = */ nif->getInt(); for(int i=0; igetInt(); - /*int y =*/ nif->getInt(); - /*int offset =*/ nif->getInt(); + Mipmap m; + m.width = nif->getUInt(); + m.height = nif->getUInt(); + m.dataOffset = nif->getUInt(); + mipmaps.push_back(m); } - // Skip the data + // Read the data unsigned int dataSize = nif->getInt(); - nif->skip(dataSize); + data.reserve(dataSize); + for (unsigned i=0; igetChar()); } void NiColorData::read(NIFStream *nif) diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 95f2441295..89886b66f8 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -105,9 +105,30 @@ public: class NiPixelData : public Record { public: + enum Format + { + NIPXFMT_RGB8, + NIPXFMT_RGBA8, + NIPXFMT_PAL8, + NIPXFMT_DXT1, + NIPXFMT_DXT3, + NIPXFMT_DXT5, + NIPXFMT_DXT5_ALT + }; + Format fmt; + unsigned int rmask, gmask, bmask, amask; int bpp, mips; + struct Mipmap + { + int width, height; + int dataOffset; + }; + std::vector mipmaps; + + std::vector data; + void read(NIFStream *nif); }; diff --git a/components/nif/effect.cpp b/components/nif/effect.cpp index 79cd104312..7947e301d6 100644 --- a/components/nif/effect.cpp +++ b/components/nif/effect.cpp @@ -19,14 +19,20 @@ void NiTextureEffect::read(NIFStream *nif) { NiDynamicEffect::read(nif); - /* - 3 x Vector4 = [1,0,0,0] - int = 2 - int = 0 or 3 - int = 2 - int = 2 - */ - nif->skip(16*4); + // Model Projection Matrix + nif->skip(3 * 3 * sizeof(float)); + + // Model Projection Transform + nif->skip(3 * sizeof(float)); + + // Texture Filtering + nif->skip(4); + + clamp = nif->getUInt(); + + textureType = (TextureType)nif->getUInt(); + + coordGenType = (CoordGenType)nif->getUInt(); texture.read(nif); diff --git a/components/nif/effect.hpp b/components/nif/effect.hpp index 02647e4448..015809a68f 100644 --- a/components/nif/effect.hpp +++ b/components/nif/effect.hpp @@ -70,6 +70,26 @@ struct NiSpotLight : public NiPointLight struct NiTextureEffect : NiDynamicEffect { NiSourceTexturePtr texture; + unsigned int clamp; + + enum TextureType + { + Projected_Light = 0, + Projected_Shadow = 1, + Environment_Map = 2, + Fog_Map = 3 + }; + TextureType textureType; + + enum CoordGenType + { + World_Parallel = 0, + World_Perspective, + Sphere_Map, + Specular_Cube_Map, + Diffuse_Cube_Map + }; + CoordGenType coordGenType; void read(NIFStream *nif); void post(NIFFile *nif); diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 326e9802fd..17b58bd463 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -9,6 +9,8 @@ #include "controller.hpp" #include "base.hpp" +#include + namespace Nif { @@ -118,10 +120,10 @@ struct NiNode : Node children.read(nif); effects.read(nif); - // Discard tranformations for the root node, otherwise some meshes + // Discard transformations for the root node, otherwise some meshes // occasionally get wrong orientation. Only for NiNode-s for now, but // can be expanded if needed. - if (0 == recIndex) + if (0 == recIndex && !Misc::StringUtils::ciEqual(name, "bip01")) { static_cast(this)->trafo = Nif::Transformation::getIdentity(); } diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 19afe49d63..9ac544c423 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -266,6 +266,11 @@ void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags, if (!shape->skin.empty()) isAnimated = false; + if (shape->data.empty()) + return; + if (shape->data->triangles->empty()) + return; + if (isAnimated) { if (!mCompoundShape) diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index 28f61e4b6c..5a3a7ac590 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -254,11 +254,17 @@ void UVController::apply(osg::StateSet* stateset, osg::NodeVisitor* nv) float uScale = mUScale.interpKey(value); float vScale = mVScale.interpKey(value); + osg::Matrix flipMat; + flipMat.preMultTranslate(osg::Vec3f(0,1,0)); + flipMat.preMultScale(osg::Vec3f(1,-1,1)); + osg::Matrixf mat = osg::Matrixf::scale(uScale, vScale, 1); mat.setTrans(uTrans, vTrans, 0); + mat = flipMat * mat * flipMat; + // setting once is enough because all other texture units share the same TexMat (see setDefaults). - if (mTextureUnits.size()) + if (!mTextureUnits.empty()) { osg::TexMat* texMat = static_cast(stateset->getTextureAttribute(*mTextureUnits.begin(), osg::StateAttribute::TEXMAT)); texMat->setMatrix(mat); @@ -433,12 +439,15 @@ ParticleSystemController::ParticleSystemController(const ParticleSystemControlle void ParticleSystemController::operator() (osg::Node* node, osg::NodeVisitor* nv) { + osgParticle::ParticleProcessor* emitter = static_cast(node); if (hasInput()) { - osgParticle::ParticleProcessor* emitter = static_cast(node); float time = getInputValue(nv); + emitter->getParticleSystem()->setFrozen(false); emitter->setEnabled(time >= mEmitStart && time < mEmitStop); } + else + emitter->getParticleSystem()->setFrozen(true); traverse(node, nv); } diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index e1e969d066..ce268f5871 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -17,7 +17,6 @@ #include #include -#include #include #include #include @@ -305,6 +304,8 @@ namespace NifOsg META_Object(NifOsg, FlipController) + std::vector >& getTextures() { return mTextures; } + virtual void apply(osg::StateSet *stateset, osg::NodeVisitor *nv); }; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 5c28bbc7e6..172d92abeb 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -2,16 +2,15 @@ #include #include -#include #include #include -#include #include +#include // resource #include #include -#include +#include // skel #include @@ -20,7 +19,6 @@ #include #include #include -#include #include #include #include @@ -37,7 +35,7 @@ #include #include -#include +#include #include #include @@ -131,31 +129,27 @@ namespace virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) { - osgUtil::CullVisitor* cv = dynamic_cast(nv); - if (node && cv) + osgUtil::CullVisitor* cv = static_cast(nv); + + osg::Matrix modelView = *cv->getModelViewMatrix(); + + // attempt to preserve scale + float mag[3]; + for (int i=0;i<3;++i) { - osg::Matrix modelView = *cv->getModelViewMatrix(); - - // attempt to preserve scale - float mag[3]; - for (int i=0;i<3;++i) - { - mag[i] = std::sqrt(modelView(0,i) * modelView(0,i) + modelView(1,i) * modelView(1,i) + modelView(2,i) * modelView(2,i)); - } - - modelView.setRotate(osg::Quat()); - modelView(0,0) = mag[0]; - modelView(1,1) = mag[1]; - modelView(2,2) = mag[2]; - - cv->pushModelViewMatrix(new osg::RefMatrix(modelView), osg::Transform::RELATIVE_RF); - - traverse(node, nv); - - cv->popModelViewMatrix(); + mag[i] = std::sqrt(modelView(0,i) * modelView(0,i) + modelView(1,i) * modelView(1,i) + modelView(2,i) * modelView(2,i)); } - else - traverse(node, nv); + + modelView.setRotate(osg::Quat()); + modelView(0,0) = mag[0]; + modelView(1,1) = mag[1]; + modelView(2,2) = mag[2]; + + cv->pushModelViewMatrix(new osg::RefMatrix(modelView), osg::Transform::RELATIVE_RF); + + traverse(node, nv); + + cv->popModelViewMatrix(); } }; @@ -339,11 +333,14 @@ namespace NifOsg osg::ref_ptr callback(new NifOsg::KeyframeController(key->data.getPtr())); callback->setFunction(boost::shared_ptr(new NifOsg::ControllerFunction(key))); - target.mKeyframeControllers[strdata->string] = callback; + if (target.mKeyframeControllers.find(strdata->string) != target.mKeyframeControllers.end()) + std::cerr << "Warning: controller " << strdata->string << " present more than once in " << nif->getFilename() << ", ignoring later version" << std::endl; + else + target.mKeyframeControllers[strdata->string] = callback; } } - osg::ref_ptr load(Nif::NIFFilePtr nif, Resource::TextureManager* textureManager) + osg::ref_ptr load(Nif::NIFFilePtr nif, Resource::ImageManager* imageManager) { if (nif->numRoots() < 1) nif->fail("Found no root nodes"); @@ -356,7 +353,7 @@ namespace NifOsg osg::ref_ptr textkeys (new TextKeyMapHolder); - osg::ref_ptr created = handleNode(nifNode, NULL, textureManager, std::vector(), 0, 0, false, &textkeys->mTextKeys); + osg::ref_ptr created = handleNode(nifNode, NULL, imageManager, std::vector(), 0, 0, false, &textkeys->mTextKeys); if (nif->getUseSkinning()) { @@ -364,18 +361,20 @@ namespace NifOsg skel->addChild(created); created = skel; } - created->getOrCreateUserDataContainer()->addUserObject(textkeys); + + if (!textkeys->mTextKeys.empty()) + created->getOrCreateUserDataContainer()->addUserObject(textkeys); return created; } - void applyNodeProperties(const Nif::Node *nifNode, osg::Node *applyTo, SceneUtil::CompositeStateSetUpdater* composite, Resource::TextureManager* textureManager, std::vector& boundTextures, int animflags) + void applyNodeProperties(const Nif::Node *nifNode, osg::Node *applyTo, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) { const Nif::PropertyList& props = nifNode->props; for (size_t i = 0; i setFunction(boost::shared_ptr(new ControllerFunction(ctrl))); } + void setupParticleController(const Nif::Controller* ctrl, SceneUtil::Controller* toSetup, int particleflags) + { + bool autoPlay = particleflags & Nif::NiNode::ParticleFlag_AutoPlay; + if (autoPlay) + toSetup->setSource(boost::shared_ptr(new SceneUtil::FrameTimeSource)); + + toSetup->setFunction(boost::shared_ptr(new ControllerFunction(ctrl))); + } + void optimize (const Nif::Node* nifNode, osg::Group* node, bool skipMeshes) { // For nodes with an identity transform, remove the redundant Transform node if (node->getDataVariance() == osg::Object::STATIC // For TriShapes, we can only collapse the node, but not completely remove it, // if the link to animated collision shapes is supposed to stay intact. - && (nifNode->recType != Nif::RC_NiTriShape || !skipMeshes)) + && (nifNode->recType != Nif::RC_NiTriShape || !skipMeshes) + // Don't optimize drawables with controllers, that creates issues when we want to deep copy controllers without deep copying the drawable that holds the controller. + // A deep copy of controllers may be needed to independently animate multiple copies of the same mesh. + && !node->getUpdateCallback()) { if (node->getNumParents() && nifNode->trafo.isIdentity()) { @@ -437,7 +448,82 @@ namespace NifOsg return lod; } - osg::ref_ptr handleNode(const Nif::Node* nifNode, osg::Group* parentNode, Resource::TextureManager* textureManager, + osg::ref_ptr handleSourceTexture(const Nif::NiSourceTexture* st, Resource::ImageManager* imageManager) + { + if (!st) + return NULL; + + osg::ref_ptr image; + if (!st->external && !st->data.empty()) + { + image = handleInternalTexture(st->data.getPtr()); + } + else + { + std::string filename = Misc::ResourceHelpers::correctTexturePath(st->filename, imageManager->getVFS()); + image = imageManager->getImage(filename); + } + return image; + } + + void handleEffect(const Nif::Node* nifNode, osg::Node* node, Resource::ImageManager* imageManager) + { + if (nifNode->recType != Nif::RC_NiTextureEffect) + { + std::cerr << "Unhandled effect " << nifNode->recName << " in " << mFilename << std::endl; + return; + } + + const Nif::NiTextureEffect* textureEffect = static_cast(nifNode); + if (textureEffect->textureType != Nif::NiTextureEffect::Environment_Map) + { + std::cerr << "Unhandled NiTextureEffect type " << textureEffect->textureType << " in " << mFilename << std::endl; + return; + } + + osg::ref_ptr texGen (new osg::TexGen); + switch (textureEffect->coordGenType) + { + case Nif::NiTextureEffect::World_Parallel: + texGen->setMode(osg::TexGen::OBJECT_LINEAR); + break; + case Nif::NiTextureEffect::World_Perspective: + texGen->setMode(osg::TexGen::EYE_LINEAR); + break; + case Nif::NiTextureEffect::Sphere_Map: + texGen->setMode(osg::TexGen::SPHERE_MAP); + break; + default: + std::cerr << "Unhandled NiTextureEffect coordGenType " << textureEffect->coordGenType << " in " << mFilename << std::endl; + return; + } + + osg::ref_ptr texture2d (new osg::Texture2D(handleSourceTexture(textureEffect->texture.getPtr(), imageManager))); + texture2d->setName("envMap"); + unsigned int clamp = static_cast(textureEffect->clamp); + int wrapT = (clamp) & 0x1; + int wrapS = (clamp >> 1) & 0x1; + texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP); + texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP); + + osg::ref_ptr texEnv = new osg::TexEnvCombine; + texEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE); + texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); + texEnv->setCombine_RGB(osg::TexEnvCombine::ADD); + texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); + texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); + + int texUnit = 3; // FIXME + + osg::StateSet* stateset = node->getOrCreateStateSet(); + stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(texUnit, texGen, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); + + stateset->addUniform(new osg::Uniform("envMapColor", osg::Vec4f(1,1,1,1))); + } + + osg::ref_ptr handleNode(const Nif::Node* nifNode, osg::Group* parentNode, Resource::ImageManager* imageManager, std::vector boundTextures, int animflags, int particleflags, bool skipMeshes, TextKeyMap* textKeys, osg::Node* rootNode=NULL) { osg::ref_ptr node = new osg::MatrixTransform(nifNode->trafo.toMatrix()); @@ -539,7 +625,7 @@ namespace NifOsg osg::ref_ptr composite = new SceneUtil::CompositeStateSetUpdater; - applyNodeProperties(nifNode, node, composite, textureManager, boundTextures, animflags); + applyNodeProperties(nifNode, node, composite, imageManager, boundTextures, animflags); if (nifNode->recType == Nif::RC_NiTriShape && !skipMeshes) { @@ -580,13 +666,18 @@ namespace NifOsg const Nif::NiNode *ninode = dynamic_cast(nifNode); if(ninode) { + const Nif::NodeList &effects = ninode->effects; + for (size_t i = 0; i < effects.length(); ++i) + { + if (!effects[i].empty()) + handleEffect(effects[i].getPtr(), node, imageManager); + } + const Nif::NodeList &children = ninode->children; for(size_t i = 0;i < children.length();++i) { if(!children[i].empty()) - { - handleNode(children[i].getPtr(), node, textureManager, boundTextures, animflags, particleflags, skipMeshes, textKeys, rootNode); - } + handleNode(children[i].getPtr(), node, imageManager, boundTextures, animflags, particleflags, skipMeshes, textKeys, rootNode); } } @@ -679,7 +770,7 @@ namespace NifOsg } } - void handleTextureControllers(const Nif::Property *texProperty, SceneUtil::CompositeStateSetUpdater* composite, Resource::TextureManager* textureManager, osg::StateSet *stateset, int animflags) + void handleTextureControllers(const Nif::Property *texProperty, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, osg::StateSet *stateset, int animflags) { for (Nif::ControllerPtr ctrl = texProperty->controller; !ctrl.empty(); ctrl = ctrl->next) { @@ -705,8 +796,9 @@ namespace NifOsg wrapT = inherit->getWrap(osg::Texture2D::WRAP_T); } - std::string filename = Misc::ResourceHelpers::correctTexturePath(st->filename, textureManager->getVFS()); - osg::ref_ptr texture = textureManager->getTexture2D(filename, wrapS, wrapT); + osg::ref_ptr texture (new osg::Texture2D(handleSourceTexture(st.getPtr(), imageManager))); + texture->setWrap(osg::Texture::WRAP_S, wrapS); + texture->setWrap(osg::Texture::WRAP_T, wrapT); textures.push_back(texture); } osg::ref_ptr callback(new FlipController(flipctrl, textures)); @@ -901,8 +993,16 @@ namespace NifOsg emitterNode->addChild(emitter); osg::ref_ptr callback(new ParticleSystemController(partctrl)); - setupController(partctrl, callback, animflags); + setupParticleController(partctrl, callback, particleflags); emitter->setUpdateCallback(callback); + + if (!(particleflags & Nif::NiNode::ParticleFlag_AutoPlay)) + { + partsys->setFrozen(true); + // HACK: particle system will not render in Frozen state if there was no update + osg::NodeVisitor nv; + partsys->update(0.0, nv); + } } // affectors must be attached *after* the emitter in the scene graph for correct update order @@ -912,16 +1012,7 @@ namespace NifOsg std::vector drawableProps; collectDrawableProperties(nifNode, drawableProps); - applyDrawableProperties(parentNode, drawableProps, composite, true, animflags); - - // Particles don't have normals, so can't be diffuse lit. - osg::Material* mat = static_cast(parentNode->getStateSet()->getAttribute(osg::StateAttribute::MATERIAL)); - if (mat) - { - // NB ignoring diffuse.a() - mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,1)); - mat->setColorMode(osg::Material::AMBIENT); - } + applyDrawableProperties(parentNode, drawableProps, composite, true, animflags, true); // particle system updater (after the emitters and affectors in the scene graph) // I think for correct culling needs to be *before* the ParticleSystem, though osg examples do it the other way @@ -929,13 +1020,7 @@ namespace NifOsg updater->addParticleSystem(partsys); parentNode->addChild(updater); -#if OSG_VERSION_LESS_THAN(3,3,3) - osg::ref_ptr geode (new osg::Geode); - geode->addDrawable(partsys); - osg::Node* toAttach = geode.get(); -#else osg::Node* toAttach = partsys.get(); -#endif if (rf == osgParticle::ParticleProcessor::RELATIVE_RF) parentNode->addChild(toAttach); @@ -964,7 +1049,7 @@ namespace NifOsg if (uvSet >= (int)data->uvlist.size()) { std::cerr << "Warning: out of bounds UV set " << uvSet << " on TriShape \"" << triShape->name << "\" in " << mFilename << std::endl; - if (data->uvlist.size()) + if (!data->uvlist.empty()) geometry->setTexCoordArray(textureStage, data->uvlist[0]); continue; } @@ -984,7 +1069,7 @@ namespace NifOsg // above the actual renderable would be tedious. std::vector drawableProps; collectDrawableProperties(triShape, drawableProps); - applyDrawableProperties(parentNode, drawableProps, composite, !data->colors->empty(), animflags); + applyDrawableProperties(parentNode, drawableProps, composite, !data->colors->empty(), animflags, false); } void handleTriShape(const Nif::NiTriShape* triShape, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) @@ -996,7 +1081,7 @@ namespace NifOsg continue; if(ctrl->recType == Nif::RC_NiGeomMorpherController) { - geometry = handleMorphGeometry(static_cast(ctrl.getPtr())); + geometry = handleMorphGeometry(static_cast(ctrl.getPtr()), triShape, parentNode, composite, boundTextures, animflags); osg::ref_ptr morphctrl = new GeomMorpherController( static_cast(ctrl.getPtr())->data.getPtr()); @@ -1007,14 +1092,10 @@ namespace NifOsg } if (!geometry.get()) + { geometry = new osg::Geometry; - - triShapeToGeometry(triShape, geometry, parentNode, composite, boundTextures, animflags); - -#if OSG_VERSION_LESS_THAN(3,3,3) - osg::ref_ptr geode (new osg::Geode); - geode->addDrawable(geometry); -#endif + triShapeToGeometry(triShape, geometry, parentNode, composite, boundTextures, animflags); + } if (geometry->getDataVariance() == osg::Object::DYNAMIC) { @@ -1023,27 +1104,17 @@ namespace NifOsg geometry->setDataVariance(osg::Object::STATIC); osg::ref_ptr frameswitch = new FrameSwitch; -#if OSG_VERSION_LESS_THAN(3,3,3) - osg::ref_ptr geode2 = static_cast(osg::clone(geode.get(), osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES)); - frameswitch->addChild(geode); - frameswitch->addChild(geode2); -#else osg::ref_ptr geom2 = static_cast(osg::clone(geometry.get(), osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES)); frameswitch->addChild(geometry); frameswitch->addChild(geom2); -#endif parentNode->addChild(frameswitch); } else -#if OSG_VERSION_LESS_THAN(3,3,3) - parentNode->addChild(geode); -#else parentNode->addChild(geometry); -#endif } - osg::ref_ptr handleMorphGeometry(const Nif::NiGeomMorpherController* morpher) + osg::ref_ptr handleMorphGeometry(const Nif::NiGeomMorpherController* morpher, const Nif::NiTriShape *triShape, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { osg::ref_ptr morphGeom = new osgAnimation::MorphGeometry; morphGeom->setMethod(osgAnimation::MorphGeometry::RELATIVE); @@ -1052,9 +1123,13 @@ namespace NifOsg morphGeom->setUpdateCallback(NULL); morphGeom->setCullCallback(new UpdateMorphGeometry); + morphGeom->setUseVertexBufferObjects(true); + morphGeom->getOrCreateVertexBufferObject()->setUsage(GL_DYNAMIC_DRAW_ARB); + + triShapeToGeometry(triShape, morphGeom, parentNode, composite, boundTextures, animflags); const std::vector& morphs = morpher->data.getPtr()->mMorphs; - if (!morphs.size()) + if (morphs.empty()) return morphGeom; // Note we are not interested in morph 0, which just contains the original vertices for (unsigned int i = 1; i < morphs.size(); ++i) @@ -1093,6 +1168,10 @@ namespace NifOsg box.expandBy(vertBounds[i]); } + // For the initial bounding box (used for object placement) use the default pose, fire off a bounding compute to set this initial box + morphGeom->getBound(); + + // Now set up the callback so that we get properly enlarged bounds if/when the mesh starts animating morphGeom->setComputeBoundingBoxCallback(new StaticBoundingBoxCallback(box)); return morphGeom; @@ -1139,21 +1218,10 @@ namespace NifOsg osg::ref_ptr frameswitch = new FrameSwitch; -#if OSG_VERSION_LESS_THAN(3,3,3) - osg::ref_ptr geode (new osg::Geode); - geode->addDrawable(rig); - - osg::Geode* geode2 = static_cast(osg::clone(geode.get(), osg::CopyOp::DEEP_COPY_NODES| - osg::CopyOp::DEEP_COPY_DRAWABLES)); - - frameswitch->addChild(geode); - frameswitch->addChild(geode2); -#else SceneUtil::RigGeometry* rig2 = static_cast(osg::clone(rig.get(), osg::CopyOp::DEEP_COPY_NODES| osg::CopyOp::DEEP_COPY_DRAWABLES)); frameswitch->addChild(rig); frameswitch->addChild(rig2); -#endif parentNode->addChild(frameswitch); } @@ -1231,8 +1299,213 @@ namespace NifOsg } } + osg::ref_ptr handleInternalTexture(const Nif::NiPixelData* pixelData) + { + osg::ref_ptr image (new osg::Image); + + GLenum pixelformat = 0; + switch (pixelData->fmt) + { + case Nif::NiPixelData::NIPXFMT_RGB8: + pixelformat = GL_RGB; + break; + case Nif::NiPixelData::NIPXFMT_RGBA8: + pixelformat = GL_RGBA; + break; + default: + std::cerr << "Unhandled internal pixel format " << pixelData->fmt << " in " << mFilename << std::endl; + return NULL; + } + + if (pixelData->mipmaps.empty()) + return NULL; + + unsigned int width = 0; + unsigned int height = 0; + + std::vector mipmapVector; + for (unsigned int i=0; imipmaps.size()-3; ++i) + { + const Nif::NiPixelData::Mipmap& mip = pixelData->mipmaps[i]; + + size_t mipSize = mip.height * mip.width * pixelData->bpp / 8; + if (mipSize + mip.dataOffset > pixelData->data.size()) + { + std::cerr << "Internal texture's mipmap data out of bounds" << std::endl; + return NULL; + } + + if (i != 0) + mipmapVector.push_back(mip.dataOffset); + else + { + width = mip.width; + height = mip.height; + } + } + + if (width <= 0 || height <= 0) + { + std::cerr << "Width and height must be non zero " << std::endl; + return NULL; + } + + unsigned char* data = new unsigned char[pixelData->data.size()]; + memcpy(data, &pixelData->data[0], pixelData->data.size()); + + image->setImage(width, height, 1, pixelformat, pixelformat, GL_UNSIGNED_BYTE, data, osg::Image::USE_NEW_DELETE); + image->setMipmapLevels(mipmapVector); + image->flipVertical(); + + return image; + } + + void handleTextureProperty(const Nif::NiTexturingProperty* texprop, osg::StateSet* stateset, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) + { + if (!boundTextures.empty()) + { + // overriding a parent NiTexturingProperty, so remove what was previously bound + for (unsigned int i=0; isetTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); + boundTextures.clear(); + } + + for (int i=0; itextures[i].inUse) + { + switch(i) + { + //These are handled later on + case Nif::NiTexturingProperty::BaseTexture: + case Nif::NiTexturingProperty::GlowTexture: + case Nif::NiTexturingProperty::DarkTexture: + case Nif::NiTexturingProperty::BumpTexture: + case Nif::NiTexturingProperty::DetailTexture: + case Nif::NiTexturingProperty::DecalTexture: + break; + case Nif::NiTexturingProperty::GlossTexture: + { + // Not used by the vanilla engine. MCP (Morrowind Code Patch) adds an option to use Gloss maps: + // "- Gloss map fix. Morrowind removed gloss map entries from model files after loading them. This stops Morrowind from removing them." + std::cerr << "NiTexturingProperty::GlossTexture in " << mFilename << " not currently used." << std::endl; + continue; + } + default: + { + std::cerr << "Warning: unhandled texture stage " << i << " in " << mFilename << std::endl; + continue; + } + } + + const Nif::NiTexturingProperty::Texture& tex = texprop->textures[i]; + if(tex.texture.empty()) + { + std::cerr << "Warning: texture layer " << i << " is in use but empty in " << mFilename << std::endl; + continue; + } + const Nif::NiSourceTexture *st = tex.texture.getPtr(); + osg::ref_ptr image = handleSourceTexture(st, imageManager); + + unsigned int clamp = static_cast(tex.clamp); + int wrapT = (clamp) & 0x1; + int wrapS = (clamp >> 1) & 0x1; + + // create a new texture, will later attempt to share using the SharedStateManager + osg::ref_ptr texture2d (new osg::Texture2D(image)); + texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP); + texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP); + + int texUnit = boundTextures.size(); + + stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); + + if (i == Nif::NiTexturingProperty::GlowTexture) + { + osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; + texEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE); + texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); + texEnv->setCombine_RGB(osg::TexEnvCombine::ADD); + texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); + texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); + + stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); + } + else if (i == Nif::NiTexturingProperty::DarkTexture) + { + osg::TexEnv* texEnv = new osg::TexEnv; + texEnv->setMode(osg::TexEnv::MODULATE); + stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); + } + else if (i == Nif::NiTexturingProperty::DetailTexture) + { + osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; + texEnv->setScale_RGB(2.f); + texEnv->setCombine_Alpha(osg::TexEnvCombine::MODULATE); + texEnv->setOperand0_Alpha(osg::TexEnvCombine::SRC_ALPHA); + texEnv->setOperand1_Alpha(osg::TexEnvCombine::SRC_ALPHA); + texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); + texEnv->setSource1_Alpha(osg::TexEnvCombine::TEXTURE); + texEnv->setCombine_RGB(osg::TexEnvCombine::MODULATE); + texEnv->setOperand0_RGB(osg::TexEnvCombine::SRC_COLOR); + texEnv->setOperand1_RGB(osg::TexEnvCombine::SRC_COLOR); + texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); + texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); + stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); + } + else if (i == Nif::NiTexturingProperty::BumpTexture) + { + // Set this texture to Off by default since we can't render it with the fixed-function pipeline + stateset->setTextureMode(texUnit, GL_TEXTURE_2D, osg::StateAttribute::OFF); + } + else if (i == Nif::NiTexturingProperty::DecalTexture) + { + osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; + texEnv->setCombine_RGB(osg::TexEnvCombine::INTERPOLATE); + texEnv->setSource0_RGB(osg::TexEnvCombine::TEXTURE); + texEnv->setOperand0_RGB(osg::TexEnvCombine::SRC_COLOR); + texEnv->setSource1_RGB(osg::TexEnvCombine::PREVIOUS); + texEnv->setOperand1_RGB(osg::TexEnvCombine::SRC_COLOR); + texEnv->setSource2_RGB(osg::TexEnvCombine::TEXTURE); + texEnv->setOperand2_RGB(osg::TexEnvCombine::SRC_ALPHA); + texEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE); + texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); + texEnv->setOperand0_Alpha(osg::TexEnvCombine::SRC_ALPHA); + stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); + } + + switch (i) + { + case Nif::NiTexturingProperty::BaseTexture: + texture2d->setName("diffuseMap"); + break; + case Nif::NiTexturingProperty::BumpTexture: + texture2d->setName("normalMap"); + break; + case Nif::NiTexturingProperty::GlowTexture: + texture2d->setName("emissiveMap"); + break; + case Nif::NiTexturingProperty::DarkTexture: + texture2d->setName("darkMap"); + break; + case Nif::NiTexturingProperty::DetailTexture: + texture2d->setName("detailMap"); + break; + case Nif::NiTexturingProperty::DecalTexture: + texture2d->setName("decalMap"); + break; + default: + break; + } + + boundTextures.push_back(tex.uvSet); + } + } + handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + } + void handleProperty(const Nif::Property *property, - osg::Node *node, SceneUtil::CompositeStateSetUpdater* composite, Resource::TextureManager* textureManager, std::vector& boundTextures, int animflags) + osg::Node *node, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) { switch (property->recType) { @@ -1304,114 +1577,7 @@ namespace NifOsg { const Nif::NiTexturingProperty* texprop = static_cast(property); osg::StateSet* stateset = node->getOrCreateStateSet(); - - if (boundTextures.size()) - { - // overriding a parent NiTexturingProperty, so remove what was previously bound - for (unsigned int i=0; isetTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); - boundTextures.clear(); - } - - for (int i=0; itextures[i].inUse) - { - switch(i) - { - //These are handled later on - case Nif::NiTexturingProperty::BaseTexture: - case Nif::NiTexturingProperty::GlowTexture: - case Nif::NiTexturingProperty::DarkTexture: - case Nif::NiTexturingProperty::DetailTexture: - break; - case Nif::NiTexturingProperty::GlossTexture: - { - std::cerr << "NiTexturingProperty::GlossTexture in " << mFilename << " not currently used." << std::endl; - continue; - } - case Nif::NiTexturingProperty::BumpTexture: - { - std::cerr << "NiTexturingProperty::BumpTexture in " << mFilename << " not currently used." << std::endl; - continue; - } - case Nif::NiTexturingProperty::DecalTexture: - { - std::cerr << "NiTexturingProperty::DecalTexture in " << mFilename << " not currently used." << std::endl; - continue; - } - default: - { - std::cerr << "Warning: unhandled texture stage " << i << " in " << mFilename << std::endl; - continue; - } - } - - const Nif::NiTexturingProperty::Texture& tex = texprop->textures[i]; - if(tex.texture.empty()) - { - std::cerr << "Warning: texture layer " << i << " is in use but empty in " << mFilename << std::endl; - continue; - } - const Nif::NiSourceTexture *st = tex.texture.getPtr(); - if (!st->external) - { - std::cerr << "Warning: unhandled internal texture in " << mFilename << std::endl; - continue; - } - - std::string filename = Misc::ResourceHelpers::correctTexturePath(st->filename, textureManager->getVFS()); - - unsigned int clamp = static_cast(tex.clamp); - int wrapT = (clamp) & 0x1; - int wrapS = (clamp >> 1) & 0x1; - - osg::ref_ptr texture2d = textureManager->getTexture2D(filename, - wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP, - wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP); - - int texUnit = boundTextures.size(); - - stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); - - if (i == Nif::NiTexturingProperty::GlowTexture) - { - osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; - texEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE); - texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); - texEnv->setCombine_RGB(osg::TexEnvCombine::ADD); - texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); - texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); - - stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); - } - else if (i == Nif::NiTexturingProperty::DarkTexture) - { - osg::TexEnv* texEnv = new osg::TexEnv; - texEnv->setMode(osg::TexEnv::MODULATE); - stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); - } - else if (i == Nif::NiTexturingProperty::DetailTexture) - { - osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; - texEnv->setScale_RGB(2.f); - texEnv->setCombine_Alpha(GL_MODULATE); - texEnv->setOperand0_Alpha(GL_SRC_ALPHA); - texEnv->setOperand1_Alpha(GL_SRC_ALPHA); - texEnv->setSource0_Alpha(GL_PREVIOUS_ARB); - texEnv->setSource1_Alpha(GL_TEXTURE); - texEnv->setCombine_RGB(GL_MODULATE); - texEnv->setOperand0_RGB(GL_SRC_COLOR); - texEnv->setOperand1_RGB(GL_SRC_COLOR); - texEnv->setSource0_RGB(GL_PREVIOUS_ARB); - texEnv->setSource1_RGB(GL_TEXTURE); - stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); - } - - boundTextures.push_back(tex.uvSet); - } - handleTextureControllers(texprop, composite, textureManager, stateset, animflags); - } + handleTextureProperty(texprop, stateset, composite, imageManager, boundTextures, animflags); break; } // unused by mw @@ -1427,8 +1593,26 @@ namespace NifOsg } } + struct CompareMaterial + { + bool operator() (const osg::ref_ptr& left, const osg::ref_ptr& right) const + { + return left->compare(*right) < 0; + } + }; + + osg::Material* shareMaterial(osg::Material* mat) + { + typedef std::set, CompareMaterial> MatCache; + static MatCache mats; + MatCache::iterator found = mats.find(mat); + if (found == mats.end()) + found = mats.insert(mat).first; + return *found; + } + void applyDrawableProperties(osg::Node* node, const std::vector& properties, SceneUtil::CompositeStateSetUpdater* composite, - bool hasVertexColors, int animflags) + bool hasVertexColors, int animflags, bool particleMaterial) { osg::StateSet* stateset = node->getOrCreateStateSet(); @@ -1525,15 +1709,27 @@ namespace NifOsg if (specFlags == 0) mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f,0.f,0.f,0.f)); + // Particles don't have normals, so can't be diffuse lit. + if (particleMaterial) + { + // NB ignoring diffuse.a() + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,1)); + mat->setColorMode(osg::Material::AMBIENT); + } + + // TODO: this could be replaced by a more generic mechanism of sharing any type of State Attribute + // apply only for Materials for now + mat = shareMaterial(mat); + stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); } }; - osg::ref_ptr Loader::load(Nif::NIFFilePtr file, Resource::TextureManager* textureManager) + osg::ref_ptr Loader::load(Nif::NIFFilePtr file, Resource::ImageManager* imageManager) { LoaderImpl impl(file->getFilename()); - return impl.load(file, textureManager); + return impl.load(file, imageManager); } void Loader::loadKf(Nif::NIFFilePtr kf, KeyframeHolder& target) diff --git a/components/nifosg/nifloader.hpp b/components/nifosg/nifloader.hpp index e15df53028..d2d5e7b2d8 100644 --- a/components/nifosg/nifloader.hpp +++ b/components/nifosg/nifloader.hpp @@ -15,7 +15,7 @@ namespace osg namespace Resource { - class TextureManager; + class ImageManager; } namespace NifOsg @@ -62,7 +62,7 @@ namespace NifOsg { public: /// Create a scene graph for the given NIF. Auto-detects when skinning is used and wraps the graph in a Skeleton if so. - static osg::ref_ptr load(Nif::NIFFilePtr file, Resource::TextureManager* textureManager); + static osg::ref_ptr load(Nif::NIFFilePtr file, Resource::ImageManager* imageManager); /// Load keyframe controllers from the given kf file. static void loadKf(Nif::NIFFilePtr kf, KeyframeHolder& target); diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp index 5003397222..f542cf379e 100644 --- a/components/nifosg/particle.cpp +++ b/components/nifosg/particle.cpp @@ -5,6 +5,8 @@ #include #include +#include +#include #include "userdata.hpp" @@ -261,10 +263,10 @@ void Emitter::emitParticles(double dt) osg::Matrix worldToPs; // maybe this could be optimized by halting at the lowest common ancestor of the particle and emitter nodes - osg::MatrixList worldMats = getParticleSystem()->getWorldMatrices(); - if (!worldMats.empty()) + osg::NodePathList partsysNodePaths = getParticleSystem()->getParentalNodePaths(); + if (!partsysNodePaths.empty()) { - const osg::Matrix psToWorld = worldMats[0]; + osg::Matrix psToWorld = osg::computeLocalToWorld(partsysNodePaths[0]); worldToPs = osg::Matrix::inverse(psToWorld); } diff --git a/components/nifosg/particle.hpp b/components/nifosg/particle.hpp index a1ed3f3d0f..6818aa7e0e 100644 --- a/components/nifosg/particle.hpp +++ b/components/nifosg/particle.hpp @@ -4,13 +4,11 @@ #include #include #include -#include +#include +#include +#include #include -#include - -#include -#include #include "controller.hpp" // ValueInterpolator @@ -18,6 +16,7 @@ namespace Nif { class NiGravity; class NiPlanarCollider; + class NiColorData; } namespace NifOsg diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp index 968dbe6c67..dbdbf0c6e3 100644 --- a/components/resource/bulletshape.cpp +++ b/components/resource/bulletshape.cpp @@ -1,6 +1,7 @@ #include "bulletshape.hpp" #include +#include #include #include @@ -16,6 +17,14 @@ BulletShape::BulletShape() } +BulletShape::BulletShape(const BulletShape ©, const osg::CopyOp ©op) + : mCollisionShape(duplicateCollisionShape(copy.mCollisionShape)) + , mCollisionBoxHalfExtents(copy.mCollisionBoxHalfExtents) + , mCollisionBoxTranslate(copy.mCollisionBoxTranslate) + , mAnimatedShapes(copy.mAnimatedShapes) +{ +} + BulletShape::~BulletShape() { deleteShape(mCollisionShape); @@ -36,11 +45,11 @@ void BulletShape::deleteShape(btCollisionShape* shape) } } -btCollisionShape* BulletShape::duplicateCollisionShape(btCollisionShape *shape) const +btCollisionShape* BulletShape::duplicateCollisionShape(const btCollisionShape *shape) const { if(shape->isCompound()) { - btCompoundShape *comp = static_cast(shape); + const btCompoundShape *comp = static_cast(shape); btCompoundShape *newShape = new btCompoundShape; int numShapes = comp->getNumChildShapes(); @@ -54,29 +63,13 @@ btCollisionShape* BulletShape::duplicateCollisionShape(btCollisionShape *shape) return newShape; } - if(btBvhTriangleMeshShape* trishape = dynamic_cast(shape)) + if(const btBvhTriangleMeshShape* trishape = dynamic_cast(shape)) { -#if BT_BULLET_VERSION >= 283 - btScaledBvhTriangleMeshShape* newShape = new btScaledBvhTriangleMeshShape(trishape, btVector3(1.f, 1.f, 1.f)); -#else - // work around btScaledBvhTriangleMeshShape bug ( https://code.google.com/p/bullet/issues/detail?id=371 ) in older bullet versions - btTriangleMesh* oldMesh = static_cast(trishape->getMeshInterface()); - btTriangleMesh* newMesh = new btTriangleMesh(*oldMesh); - - // Do not build a new bvh (not needed, since it's the same as the original shape's bvh) - bool buildBvh = true; - if (trishape->getOptimizedBvh()) - buildBvh = false; - TriangleMeshShape* newShape = new TriangleMeshShape(newMesh, true, buildBvh); - // Set original shape's bvh via pointer - // The pointer is safe because the BulletShapeInstance keeps a ref_ptr to the original BulletShape - if (!buildBvh) - newShape->setOptimizedBvh(trishape->getOptimizedBvh()); -#endif + btScaledBvhTriangleMeshShape* newShape = new btScaledBvhTriangleMeshShape(const_cast(trishape), btVector3(1.f, 1.f, 1.f)); return newShape; } - if (btBoxShape* boxshape = dynamic_cast(shape)) + if (const btBoxShape* boxshape = dynamic_cast(shape)) { return new btBoxShape(*boxshape); } @@ -89,13 +82,13 @@ btCollisionShape *BulletShape::getCollisionShape() return mCollisionShape; } -osg::ref_ptr BulletShape::makeInstance() +osg::ref_ptr BulletShape::makeInstance() const { osg::ref_ptr instance (new BulletShapeInstance(this)); return instance; } -BulletShapeInstance::BulletShapeInstance(osg::ref_ptr source) +BulletShapeInstance::BulletShapeInstance(osg::ref_ptr source) : BulletShape() , mSource(source) { diff --git a/components/resource/bulletshape.hpp b/components/resource/bulletshape.hpp index cfae27eac4..a418bb28ce 100644 --- a/components/resource/bulletshape.hpp +++ b/components/resource/bulletshape.hpp @@ -3,7 +3,7 @@ #include -#include +#include #include #include @@ -15,12 +15,15 @@ namespace Resource { class BulletShapeInstance; - class BulletShape : public osg::Referenced + class BulletShape : public osg::Object { public: BulletShape(); + BulletShape(const BulletShape& copy, const osg::CopyOp& copyop); virtual ~BulletShape(); + META_Object(Resource, BulletShape) + btCollisionShape* mCollisionShape; // Used for actors. Note, ideally actors would use a separate loader - as it is @@ -35,13 +38,14 @@ namespace Resource // we store the node's record index mapped to the child index of the shape in the btCompoundShape. std::map mAnimatedShapes; - osg::ref_ptr makeInstance(); + osg::ref_ptr makeInstance() const; - btCollisionShape* duplicateCollisionShape(btCollisionShape* shape) const; + btCollisionShape* duplicateCollisionShape(const btCollisionShape* shape) const; btCollisionShape* getCollisionShape(); private: + void deleteShape(btCollisionShape* shape); }; @@ -51,10 +55,10 @@ namespace Resource class BulletShapeInstance : public BulletShape { public: - BulletShapeInstance(osg::ref_ptr source); + BulletShapeInstance(osg::ref_ptr source); private: - osg::ref_ptr mSource; + osg::ref_ptr mSource; }; // Subclass btBhvTriangleMeshShape to auto-delete the meshInterface diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index 4cbe62f3c3..0a5ed783ef 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -1,8 +1,9 @@ #include "bulletshapemanager.hpp" #include -#include #include +#include +#include #include @@ -13,7 +14,8 @@ #include "bulletshape.hpp" #include "scenemanager.hpp" #include "niffilemanager.hpp" - +#include "objectcache.hpp" +#include "multiobjectcache.hpp" namespace Resource { @@ -61,12 +63,6 @@ public: } - virtual void apply(osg::Geode& geode) - { - for (unsigned int i=0; i BulletShapeManager::createInstance(const std::string &name) +osg::ref_ptr BulletShapeManager::getShape(const std::string &name) { std::string normalized = name; mVFS->normalizeFilename(normalized); osg::ref_ptr shape; - Index::iterator it = mIndex.find(normalized); - if (it == mIndex.end()) + osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); + if (obj) + shape = osg::ref_ptr(static_cast(obj.get())); + else { size_t extPos = normalized.find_last_of('.'); std::string ext; @@ -137,16 +136,53 @@ osg::ref_ptr BulletShapeManager::createInstance(const std:: node->accept(visitor); shape = visitor.getShape(); if (!shape) - return osg::ref_ptr(); + { + mCache->addEntryToObjectCache(normalized, NULL); + return osg::ref_ptr(); + } } - mIndex[normalized] = shape; + mCache->addEntryToObjectCache(normalized, shape); } - else - shape = it->second; + return shape; +} - osg::ref_ptr instance = shape->makeInstance(); +osg::ref_ptr BulletShapeManager::cacheInstance(const std::string &name) +{ + std::string normalized = name; + mVFS->normalizeFilename(normalized); + + osg::ref_ptr instance = createInstance(normalized); + mInstanceCache->addEntryToObjectCache(normalized, instance.get()); return instance; } +osg::ref_ptr BulletShapeManager::getInstance(const std::string &name) +{ + std::string normalized = name; + mVFS->normalizeFilename(normalized); + + osg::ref_ptr obj = mInstanceCache->takeFromObjectCache(normalized); + if (obj.get()) + return static_cast(obj.get()); + else + return createInstance(normalized); +} + +osg::ref_ptr BulletShapeManager::createInstance(const std::string &name) +{ + osg::ref_ptr shape = getShape(name); + if (shape) + return shape->makeInstance(); + else + return osg::ref_ptr(); +} + +void BulletShapeManager::updateCache(double referenceTime) +{ + ResourceManager::updateCache(referenceTime); + + mInstanceCache->removeUnreferencedObjectsInCache(); +} + } diff --git a/components/resource/bulletshapemanager.hpp b/components/resource/bulletshapemanager.hpp index ac1523495c..14b26962ba 100644 --- a/components/resource/bulletshapemanager.hpp +++ b/components/resource/bulletshapemanager.hpp @@ -7,11 +7,7 @@ #include #include "bulletshape.hpp" - -namespace VFS -{ - class Manager; -} +#include "resourcemanager.hpp" namespace Resource { @@ -21,21 +17,37 @@ namespace Resource class BulletShape; class BulletShapeInstance; - class BulletShapeManager + class MultiObjectCache; + + /// Handles loading, caching and "instancing" of bullet shapes. + /// A shape 'instance' is a clone of another shape, with the goal of setting a different scale on this instance. + /// @note May be used from any thread. + class BulletShapeManager : public ResourceManager { public: BulletShapeManager(const VFS::Manager* vfs, SceneManager* sceneMgr, NifFileManager* nifFileManager); ~BulletShapeManager(); - osg::ref_ptr createInstance(const std::string& name); + /// @note May return a null pointer if the object has no shape. + osg::ref_ptr getShape(const std::string& name); + + /// Create an instance of the given shape and cache it for later use, so that future calls to getInstance() can simply return + /// the cached instance instead of having to create a new one. + /// @note The returned ref_ptr may be kept by the caller to ensure that the instance stays in cache for as long as needed. + osg::ref_ptr cacheInstance(const std::string& name); + + /// @note May return a null pointer if the object has no shape. + osg::ref_ptr getInstance(const std::string& name); + + /// @see ResourceManager::updateCache + virtual void updateCache(double referenceTime); private: - const VFS::Manager* mVFS; + osg::ref_ptr createInstance(const std::string& name); + + osg::ref_ptr mInstanceCache; SceneManager* mSceneManager; NifFileManager* mNifFileManager; - - typedef std::map > Index; - Index mIndex; }; } diff --git a/components/resource/imagemanager.cpp b/components/resource/imagemanager.cpp new file mode 100644 index 0000000000..ea3adc704b --- /dev/null +++ b/components/resource/imagemanager.cpp @@ -0,0 +1,141 @@ +#include "imagemanager.hpp" + +#include +#include + +#include + +#include "objectcache.hpp" + +#ifdef OSG_LIBRARY_STATIC +// This list of plugins should match with the list in the top-level CMakelists.txt. +USE_OSGPLUGIN(png) +USE_OSGPLUGIN(tga) +USE_OSGPLUGIN(dds) +USE_OSGPLUGIN(jpeg) +#endif + +namespace +{ + + osg::ref_ptr createWarningImage() + { + osg::ref_ptr warningImage = new osg::Image; + + int width = 8, height = 8; + warningImage->allocateImage(width, height, 1, GL_RGB, GL_UNSIGNED_BYTE); + assert (warningImage->isDataContiguous()); + unsigned char* data = warningImage->data(); + for (int i=0;igetPixelFormat()) + { + case(GL_COMPRESSED_RGB_S3TC_DXT1_EXT): + case(GL_COMPRESSED_RGBA_S3TC_DXT1_EXT): + case(GL_COMPRESSED_RGBA_S3TC_DXT3_EXT): + case(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT): + { + osg::GLExtensions* exts = osg::GLExtensions::Get(0, false); + if (exts && !exts->isTextureCompressionS3TCSupported + // This one works too. Should it be included in isTextureCompressionS3TCSupported()? Submitted as a patch to OSG. + && !osg::isGLExtensionSupported(0, "GL_S3_s3tc")) + { + std::cerr << "Error loading " << filename << ": no S3TC texture compression support installed" << std::endl; + return false; + } + break; + } + // not bothering with checks for other compression formats right now, we are unlikely to ever use those anyway + default: + return true; + } + return true; + } + + osg::ref_ptr ImageManager::getImage(const std::string &filename) + { + std::string normalized = filename; + mVFS->normalizeFilename(normalized); + + osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); + if (obj) + return osg::ref_ptr(static_cast(obj.get())); + else + { + Files::IStreamPtr stream; + try + { + stream = mVFS->get(normalized.c_str()); + } + catch (std::exception& e) + { + std::cerr << "Failed to open image: " << e.what() << std::endl; + mCache->addEntryToObjectCache(normalized, mWarningImage); + return mWarningImage; + } + + size_t extPos = normalized.find_last_of('.'); + std::string ext; + if (extPos != std::string::npos && extPos+1 < normalized.size()) + ext = normalized.substr(extPos+1); + osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(ext); + if (!reader) + { + std::cerr << "Error loading " << filename << ": no readerwriter for '" << ext << "' found" << std::endl; + mCache->addEntryToObjectCache(normalized, mWarningImage); + return mWarningImage; + } + + osgDB::ReaderWriter::ReadResult result = reader->readImage(*stream, mOptions); + if (!result.success()) + { + std::cerr << "Error loading " << filename << ": " << result.message() << " code " << result.status() << std::endl; + mCache->addEntryToObjectCache(normalized, mWarningImage); + return mWarningImage; + } + + osg::Image* image = result.getImage(); + image->setFileName(normalized); + if (!checkSupported(image, filename)) + { + mCache->addEntryToObjectCache(normalized, mWarningImage); + return mWarningImage; + } + + mCache->addEntryToObjectCache(normalized, image); + return image; + } + } + + osg::Image *ImageManager::getWarningImage() + { + return mWarningImage; + } + +} diff --git a/components/resource/imagemanager.hpp b/components/resource/imagemanager.hpp new file mode 100644 index 0000000000..8d9ad2c327 --- /dev/null +++ b/components/resource/imagemanager.hpp @@ -0,0 +1,50 @@ +#ifndef OPENMW_COMPONENTS_RESOURCE_IMAGEMANAGER_H +#define OPENMW_COMPONENTS_RESOURCE_IMAGEMANAGER_H + +#include +#include + +#include +#include +#include + +#include "resourcemanager.hpp" + +namespace osgViewer +{ + class Viewer; +} + +namespace osgDB +{ + class Options; +} + +namespace Resource +{ + + /// @brief Handles loading/caching of Images. + /// @note May be used from any thread. + class ImageManager : public ResourceManager + { + public: + ImageManager(const VFS::Manager* vfs); + ~ImageManager(); + + /// Create or retrieve an Image + /// Returns the dummy image if the given image is not found. + osg::ref_ptr getImage(const std::string& filename); + + osg::Image* getWarningImage(); + + private: + osg::ref_ptr mWarningImage; + osg::ref_ptr mOptions; + + ImageManager(const ImageManager&); + void operator = (const ImageManager&); + }; + +} + +#endif diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 7e948dcb03..4392f84c19 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -9,8 +9,7 @@ namespace Resource { KeyframeManager::KeyframeManager(const VFS::Manager* vfs) - : mCache(new osgDB::ObjectCache) - , mVFS(vfs) + : ResourceManager(vfs) { } diff --git a/components/resource/keyframemanager.hpp b/components/resource/keyframemanager.hpp index 5032d0e389..1c2c219bb1 100644 --- a/components/resource/keyframemanager.hpp +++ b/components/resource/keyframemanager.hpp @@ -4,15 +4,7 @@ #include #include -namespace VFS -{ - class Manager; -} - -namespace osgDB -{ - class ObjectCache; -} +#include "resourcemanager.hpp" namespace NifOsg { @@ -23,23 +15,16 @@ namespace Resource { /// @brief Managing of keyframe resources - class KeyframeManager + /// @note May be used from any thread. + class KeyframeManager : public ResourceManager { public: KeyframeManager(const VFS::Manager* vfs); ~KeyframeManager(); - void clearCache(); - /// Retrieve a read-only keyframe resource by name (case-insensitive). - /// @note This method is safe to call from any thread. /// @note Throws an exception if the resource is not found. osg::ref_ptr get(const std::string& name); - - private: - osg::ref_ptr mCache; - - const VFS::Manager* mVFS; }; } diff --git a/components/resource/multiobjectcache.cpp b/components/resource/multiobjectcache.cpp new file mode 100644 index 0000000000..352715f19f --- /dev/null +++ b/components/resource/multiobjectcache.cpp @@ -0,0 +1,79 @@ +#include "multiobjectcache.hpp" + +#include + +#include + +namespace Resource +{ + + MultiObjectCache::MultiObjectCache() + { + + } + + MultiObjectCache::~MultiObjectCache() + { + + } + + void MultiObjectCache::removeUnreferencedObjectsInCache() + { + std::vector > objectsToRemove; + { + OpenThreads::ScopedLock lock(_objectCacheMutex); + + // Remove unreferenced entries from object cache + ObjectCacheMap::iterator oitr = _objectCache.begin(); + while(oitr != _objectCache.end()) + { + if (oitr->second->referenceCount() <= 1) + { + objectsToRemove.push_back(oitr->second); + _objectCache.erase(oitr++); + } + else + { + ++oitr; + } + } + } + + // note, actual unref happens outside of the lock + objectsToRemove.clear(); + } + + void MultiObjectCache::addEntryToObjectCache(const std::string &filename, osg::Object *object) + { + OpenThreads::ScopedLock lock(_objectCacheMutex); + _objectCache.insert(std::make_pair(filename, object)); + } + + osg::ref_ptr MultiObjectCache::takeFromObjectCache(const std::string &fileName) + { + OpenThreads::ScopedLock lock(_objectCacheMutex); + ObjectCacheMap::iterator found = _objectCache.find(fileName); + if (found == _objectCache.end()) + return osg::ref_ptr(); + else + { + osg::ref_ptr object = found->second; + _objectCache.erase(found); + return object; + } + } + + void MultiObjectCache::releaseGLObjects(osg::State *state) + { + OpenThreads::ScopedLock lock(_objectCacheMutex); + + for(ObjectCacheMap::iterator itr = _objectCache.begin(); + itr != _objectCache.end(); + ++itr) + { + osg::Object* object = itr->second.get(); + object->releaseGLObjects(state); + } + } + +} diff --git a/components/resource/multiobjectcache.hpp b/components/resource/multiobjectcache.hpp new file mode 100644 index 0000000000..b677100f08 --- /dev/null +++ b/components/resource/multiobjectcache.hpp @@ -0,0 +1,47 @@ +#ifndef OPENMW_COMPONENTS_MULTIOBJECTCACHE_H +#define OPENMW_COMPONENTS_MULTIOBJECTCACHE_H + +#include +#include + +#include +#include + +namespace osg +{ + class Object; + class State; +} + +namespace Resource +{ + + /// @brief Cache for "non reusable" objects. + class MultiObjectCache : public osg::Referenced + { + public: + MultiObjectCache(); + ~MultiObjectCache(); + + void removeUnreferencedObjectsInCache(); + + void addEntryToObjectCache(const std::string& filename, osg::Object* object); + + /** Take an Object from cache. Return NULL if no object found. */ + osg::ref_ptr takeFromObjectCache(const std::string& fileName); + + /** call releaseGLObjects on all objects attached to the object cache.*/ + void releaseGLObjects(osg::State* state); + + protected: + + typedef std::multimap > ObjectCacheMap; + + ObjectCacheMap _objectCache; + OpenThreads::Mutex _objectCacheMutex; + + }; + +} + +#endif diff --git a/components/resource/niffilemanager.cpp b/components/resource/niffilemanager.cpp index 1d8019b69d..82768cc2c5 100644 --- a/components/resource/niffilemanager.cpp +++ b/components/resource/niffilemanager.cpp @@ -1,5 +1,7 @@ #include "niffilemanager.hpp" +#include + #include #include "objectcache.hpp" @@ -29,9 +31,8 @@ namespace Resource }; NifFileManager::NifFileManager(const VFS::Manager *vfs) - : mVFS(vfs) + : ResourceManager(vfs) { - mCache = new osgDB::ObjectCache; } NifFileManager::~NifFileManager() @@ -39,12 +40,6 @@ namespace Resource } - void NifFileManager::clearCache() - { - // NIF files aren't needed any more when the converted objects are cached in SceneManager / BulletShapeManager, - // so we'll simply drop all nif files here, unlikely to need them again - mCache->clear(); - } Nif::NIFFilePtr NifFileManager::get(const std::string &name) { diff --git a/components/resource/niffilemanager.hpp b/components/resource/niffilemanager.hpp index 90ad9fc294..4b43ff24bf 100644 --- a/components/resource/niffilemanager.hpp +++ b/components/resource/niffilemanager.hpp @@ -5,39 +5,23 @@ #include -namespace VFS -{ - class Manager; -} - -namespace osgDB -{ - class ObjectCache; -} +#include "resourcemanager.hpp" namespace Resource { /// @brief Handles caching of NIFFiles. - /// @note The NifFileManager is completely thread safe. - class NifFileManager + /// @note May be used from any thread. + class NifFileManager : public ResourceManager { public: NifFileManager(const VFS::Manager* vfs); ~NifFileManager(); - void clearCache(); - /// Retrieve a NIF file from the cache, or load it from the VFS if not cached yet. /// @note For performance reasons the NifFileManager does not handle case folding, needs /// to be done in advance by other managers accessing the NifFileManager. Nif::NIFFilePtr get(const std::string& name); - - private: - // Use the osgDB::ObjectCache so objects are retrieved in thread safe way - osg::ref_ptr mCache; - - const VFS::Manager* mVFS; }; } diff --git a/components/resource/objectcache.cpp b/components/resource/objectcache.cpp index 7264d05aa7..7caf5366c9 100644 --- a/components/resource/objectcache.cpp +++ b/components/resource/objectcache.cpp @@ -11,13 +11,13 @@ * OpenSceneGraph Public License for more details. */ -#include - -#if OSG_VERSION_LESS_THAN(3,3,3) - #include "objectcache.hpp" -using namespace osgDB; +#include +#include + +namespace Resource +{ //////////////////////////////////////////////////////////////////////////////////////////// // @@ -26,50 +26,24 @@ using namespace osgDB; ObjectCache::ObjectCache(): osg::Referenced(true) { -// OSG_NOTICE<<"Constructed ObjectCache"< lock1(_objectCacheMutex); - OpenThreads::ScopedLock lock2(objectCache->_objectCacheMutex); - - // OSG_NOTICE<<"Inserting objects to main ObjectCache "<_objectCache.size()<_objectCache.begin(), objectCache->_objectCache.end()); -} - - void ObjectCache::addEntryToObjectCache(const std::string& filename, osg::Object* object, double timestamp) { OpenThreads::ScopedLock lock(_objectCacheMutex); _objectCache[filename]=ObjectTimeStampPair(object,timestamp); } -osg::Object* ObjectCache::getFromObjectCache(const std::string& fileName) -{ - OpenThreads::ScopedLock lock(_objectCacheMutex); - ObjectCacheMap::iterator itr = _objectCache.find(fileName); - if (itr!=_objectCache.end()) return itr->second.first.get(); - else return 0; -} - osg::ref_ptr ObjectCache::getRefFromObjectCache(const std::string& fileName) { OpenThreads::ScopedLock lock(_objectCacheMutex); ObjectCacheMap::iterator itr = _objectCache.find(fileName); if (itr!=_objectCache.end()) { - // OSG_NOTICE<<"Found "<second.first; } else return 0; @@ -95,21 +69,29 @@ void ObjectCache::updateTimeStampOfObjectsInCacheWithExternalReferences(double r void ObjectCache::removeExpiredObjectsInCache(double expiryTime) { - OpenThreads::ScopedLock lock(_objectCacheMutex); + std::vector > objectsToRemove; - // Remove expired entries from object cache - ObjectCacheMap::iterator oitr = _objectCache.begin(); - while(oitr != _objectCache.end()) { - if (oitr->second.second<=expiryTime) + OpenThreads::ScopedLock lock(_objectCacheMutex); + + // Remove expired entries from object cache + ObjectCacheMap::iterator oitr = _objectCache.begin(); + while(oitr != _objectCache.end()) { - _objectCache.erase(oitr++); - } - else - { - ++oitr; + if (oitr->second.second<=expiryTime) + { + objectsToRemove.push_back(oitr->second.first); + _objectCache.erase(oitr++); + } + else + { + ++oitr; + } } } + + // note, actual unref happens outside of the lock + objectsToRemove.clear(); } void ObjectCache::removeFromObjectCache(const std::string& fileName) @@ -138,4 +120,22 @@ void ObjectCache::releaseGLObjects(osg::State* state) } } -#endif +void ObjectCache::accept(osg::NodeVisitor &nv) +{ + OpenThreads::ScopedLock lock(_objectCacheMutex); + + for(ObjectCacheMap::iterator itr = _objectCache.begin(); + itr != _objectCache.end(); + ++itr) + { + osg::Object* object = itr->second.first.get(); + if (object) + { + osg::Node* node = dynamic_cast(object); + if (node) + node->accept(nv); + } + } +} + +} diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index cae48ca6b7..82aca76f03 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -1,3 +1,6 @@ +// Resource ObjectCache for OpenMW, forked from osgDB ObjectCache by Robert Osfield, see copyright notice below. +// The main change from the upstream version is that removeExpiredObjectsInCache no longer keeps a lock while the unref happens. + /* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield * * This library is open source and may be redistributed and/or modified under @@ -11,28 +14,25 @@ * OpenSceneGraph Public License for more details. */ -// Wrapper for osgDB/ObjectCache. Works around ObjectCache not being available in old OSG 3.2. -// Use "#include objectcache.hpp" in place of "#include - -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) -#include -#else - -#include - -#include -#include +#include +#include +#include #include -namespace osgDB { +namespace osg +{ + class Object; + class State; + class NodeVisitor; +} -class /*OSGDB_EXPORT*/ ObjectCache : public osg::Referenced +namespace Resource { + +class ObjectCache : public osg::Referenced { public: @@ -55,24 +55,21 @@ class /*OSGDB_EXPORT*/ ObjectCache : public osg::Referenced /** Remove all objects in the cache regardless of having external references or expiry times.*/ void clear(); - /** Add contents of specified ObjectCache to this object cache.*/ - void addObjectCache(ObjectCache* object); - /** Add a filename,object,timestamp triple to the Registry::ObjectCache.*/ void addEntryToObjectCache(const std::string& filename, osg::Object* object, double timestamp = 0.0); /** Remove Object from cache.*/ void removeFromObjectCache(const std::string& fileName); - /** Get an Object from the object cache*/ - osg::Object* getFromObjectCache(const std::string& fileName); - /** Get an ref_ptr from the object cache*/ osg::ref_ptr getRefFromObjectCache(const std::string& fileName); - /** call rleaseGLObjects on all objects attached to the object cache.*/ + /** call releaseGLObjects on all objects attached to the object cache.*/ void releaseGLObjects(osg::State* state); + /** call node->accept(nv); for all nodes in the objectCache. */ + void accept(osg::NodeVisitor& nv); + protected: virtual ~ObjectCache(); @@ -88,5 +85,3 @@ class /*OSGDB_EXPORT*/ ObjectCache : public osg::Referenced } #endif - -#endif diff --git a/components/resource/resourcemanager.cpp b/components/resource/resourcemanager.cpp new file mode 100644 index 0000000000..d19d9cf804 --- /dev/null +++ b/components/resource/resourcemanager.cpp @@ -0,0 +1,36 @@ +#include "resourcemanager.hpp" + +#include "objectcache.hpp" + +namespace Resource +{ + + ResourceManager::ResourceManager(const VFS::Manager *vfs) + : mVFS(vfs) + , mCache(new Resource::ObjectCache) + , mExpiryDelay(0.0) + { + + } + + ResourceManager::~ResourceManager() + { + } + + void ResourceManager::updateCache(double referenceTime) + { + mCache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); + mCache->removeExpiredObjectsInCache(referenceTime - mExpiryDelay); + } + + void ResourceManager::setExpiryDelay(double expiryDelay) + { + mExpiryDelay = expiryDelay; + } + + const VFS::Manager* ResourceManager::getVFS() const + { + return mVFS; + } + +} diff --git a/components/resource/resourcemanager.hpp b/components/resource/resourcemanager.hpp new file mode 100644 index 0000000000..b69256834e --- /dev/null +++ b/components/resource/resourcemanager.hpp @@ -0,0 +1,39 @@ +#ifndef OPENMW_COMPONENTS_RESOURCE_MANAGER_H +#define OPENMW_COMPONENTS_RESOURCE_MANAGER_H + +#include + +namespace VFS +{ + class Manager; +} + +namespace Resource +{ + class ObjectCache; + + /// @brief Base class for managers that require a virtual file system and object cache. + /// @par This base class implements clearing of the cache, but populating it and what it's used for is up to the individual sub classes. + class ResourceManager + { + public: + ResourceManager(const VFS::Manager* vfs); + virtual ~ResourceManager(); + + /// Clear cache entries that have not been referenced for longer than expiryDelay. + virtual void updateCache(double referenceTime); + + /// How long to keep objects in cache after no longer being referenced. + void setExpiryDelay (double expiryDelay); + + const VFS::Manager* getVFS() const; + + protected: + const VFS::Manager* mVFS; + osg::ref_ptr mCache; + double mExpiryDelay; + }; + +} + +#endif diff --git a/components/resource/resourcesystem.cpp b/components/resource/resourcesystem.cpp index 2ce8d22e6a..5772f2b853 100644 --- a/components/resource/resourcesystem.cpp +++ b/components/resource/resourcesystem.cpp @@ -1,7 +1,7 @@ #include "resourcesystem.hpp" #include "scenemanager.hpp" -#include "texturemanager.hpp" +#include "imagemanager.hpp" #include "niffilemanager.hpp" #include "keyframemanager.hpp" @@ -13,13 +13,23 @@ namespace Resource { mNifFileManager.reset(new NifFileManager(vfs)); mKeyframeManager.reset(new KeyframeManager(vfs)); - mTextureManager.reset(new TextureManager(vfs)); - mSceneManager.reset(new SceneManager(vfs, mTextureManager.get(), mNifFileManager.get())); + mImageManager.reset(new ImageManager(vfs)); + mSceneManager.reset(new SceneManager(vfs, mImageManager.get(), mNifFileManager.get())); + + addResourceManager(mNifFileManager.get()); + addResourceManager(mKeyframeManager.get()); + // note, scene references images so add images afterwards for correct implementation of updateCache() + addResourceManager(mSceneManager.get()); + addResourceManager(mImageManager.get()); } ResourceSystem::~ResourceSystem() { // this has to be defined in the .cpp file as we can't delete incomplete types + + mResourceManagers.clear(); + + // no delete, all handled by auto_ptr } SceneManager* ResourceSystem::getSceneManager() @@ -27,9 +37,9 @@ namespace Resource return mSceneManager.get(); } - TextureManager* ResourceSystem::getTextureManager() + ImageManager* ResourceSystem::getImageManager() { - return mTextureManager.get(); + return mImageManager.get(); } NifFileManager* ResourceSystem::getNifFileManager() @@ -42,9 +52,32 @@ namespace Resource return mKeyframeManager.get(); } - void ResourceSystem::clearCache() + void ResourceSystem::setExpiryDelay(double expiryDelay) { - mNifFileManager->clearCache(); + for (std::vector::iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it) + (*it)->setExpiryDelay(expiryDelay); + + // NIF files aren't needed any more once the converted objects are cached in SceneManager / BulletShapeManager, + // so no point in using an expiry delay + mNifFileManager->setExpiryDelay(0.0); + } + + void ResourceSystem::updateCache(double referenceTime) + { + for (std::vector::iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it) + (*it)->updateCache(referenceTime); + } + + void ResourceSystem::addResourceManager(ResourceManager *resourceMgr) + { + mResourceManagers.push_back(resourceMgr); + } + + void ResourceSystem::removeResourceManager(ResourceManager *resourceMgr) + { + std::vector::iterator found = std::find(mResourceManagers.begin(), mResourceManagers.end(), resourceMgr); + if (found != mResourceManagers.end()) + mResourceManagers.erase(found); } const VFS::Manager* ResourceSystem::getVFS() const diff --git a/components/resource/resourcesystem.hpp b/components/resource/resourcesystem.hpp index 3e1a793cac..9b933ffc4d 100644 --- a/components/resource/resourcesystem.hpp +++ b/components/resource/resourcesystem.hpp @@ -2,6 +2,7 @@ #define OPENMW_COMPONENTS_RESOURCE_RESOURCESYSTEM_H #include +#include namespace VFS { @@ -12,9 +13,10 @@ namespace Resource { class SceneManager; - class TextureManager; + class ImageManager; class NifFileManager; class KeyframeManager; + class ResourceManager; /// @brief Wrapper class that constructs and provides access to the most commonly used resource subsystems. /// @par Resource subsystems can be used with multiple OpenGL contexts, just like the OSG equivalents, but @@ -26,21 +28,37 @@ namespace Resource ~ResourceSystem(); SceneManager* getSceneManager(); - TextureManager* getTextureManager(); + ImageManager* getImageManager(); NifFileManager* getNifFileManager(); KeyframeManager* getKeyframeManager(); /// Indicates to each resource manager to clear the cache, i.e. to drop cached objects that are no longer referenced. - void clearCache(); + /// @note May be called from any thread if you do not add or remove resource managers at that point. + void updateCache(double referenceTime); + /// Add this ResourceManager to be handled by the ResourceSystem. + /// @note Does not transfer ownership. + void addResourceManager(ResourceManager* resourceMgr); + /// @note Do nothing if resourceMgr does not exist. + /// @note Does not delete resourceMgr. + void removeResourceManager(ResourceManager* resourceMgr); + + /// How long to keep objects in cache after no longer being referenced. + void setExpiryDelay(double expiryDelay); + + /// @note May be called from any thread. const VFS::Manager* getVFS() const; private: std::auto_ptr mSceneManager; - std::auto_ptr mTextureManager; + std::auto_ptr mImageManager; std::auto_ptr mNifFileManager; std::auto_ptr mKeyframeManager; + // Store the base classes separately to get convenient access to the common interface + // Here users can register their own resourcemanager as well + std::vector mResourceManagers; + const VFS::Manager* mVFS; ResourceSystem(const ResourceSystem&); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 1d1c5a3656..e4fc459ac4 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -1,9 +1,8 @@ #include "scenemanager.hpp" +#include #include -#include #include -#include #include @@ -19,13 +18,20 @@ #include #include +#include -#include "texturemanager.hpp" +#include +#include + +#include "imagemanager.hpp" #include "niffilemanager.hpp" +#include "objectcache.hpp" +#include "multiobjectcache.hpp" namespace { + /// @todo Do this in updateCallback so that animations are accounted for. class InitWorldSpaceParticlesVisitor : public osg::NodeVisitor { public: @@ -44,25 +50,6 @@ namespace && partsys->getUserDataContainer()->getDescriptions()[0] == "worldspace"); } - void apply(osg::Geode& geode) - { - for (unsigned int i=0;i(geode.getDrawable(i))) - { - if (isWorldSpaceParticleSystem(partsys)) - { - // HACK: Ignore the InverseWorldMatrix transform the geode is attached to - if (geode.getNumParents() && geode.getParent(0)->getNumParents()) - transformInitialParticles(partsys, geode.getParent(0)->getParent(0)); - } - geode.setNodeMask(mMask); - } - } - } - -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) - // in OSG 3.3 and up Drawables can be directly in the scene graph without a Geode decorating them. void apply(osg::Drawable& drw) { if (osgParticle::ParticleSystem* partsys = dynamic_cast(&drw)) @@ -76,14 +63,13 @@ namespace partsys->setNodeMask(mMask); } } -#endif void transformInitialParticles(osgParticle::ParticleSystem* partsys, osg::Node* node) { - osg::MatrixList mats = node->getWorldMatrices(); - if (mats.empty()) + osg::NodePathList nodepaths = node->getParentalNodePaths(); + if (nodepaths.empty()) return; - osg::Matrixf worldMat = mats[0]; + osg::Matrixf worldMat = osg::computeLocalToWorld(nodepaths[0]); worldMat.orthoNormalize(worldMat); // scale is already applied on the particle node for (int i=0; inumParticles(); ++i) { @@ -105,26 +91,192 @@ namespace namespace Resource { - SceneManager::SceneManager(const VFS::Manager *vfs, Resource::TextureManager* textureManager, Resource::NifFileManager* nifFileManager) - : mVFS(vfs) - , mTextureManager(textureManager) + /// Set texture filtering settings on textures contained in a FlipController. + class SetFilterSettingsControllerVisitor : public SceneUtil::ControllerVisitor + { + public: + SetFilterSettingsControllerVisitor(osg::Texture::FilterMode minFilter, osg::Texture::FilterMode magFilter, int maxAnisotropy) + : mMinFilter(minFilter) + , mMagFilter(magFilter) + , mMaxAnisotropy(maxAnisotropy) + { + } + + virtual void visit(osg::Node& node, SceneUtil::Controller& ctrl) + { + if (NifOsg::FlipController* flipctrl = dynamic_cast(&ctrl)) + { + for (std::vector >::iterator it = flipctrl->getTextures().begin(); it != flipctrl->getTextures().end(); ++it) + { + osg::Texture* tex = *it; + tex->setFilter(osg::Texture::MIN_FILTER, mMinFilter); + tex->setFilter(osg::Texture::MAG_FILTER, mMagFilter); + tex->setMaxAnisotropy(mMaxAnisotropy); + } + } + } + + private: + osg::Texture::FilterMode mMinFilter; + osg::Texture::FilterMode mMagFilter; + int mMaxAnisotropy; + }; + + /// Set texture filtering settings on textures contained in StateSets. + class SetFilterSettingsVisitor : public osg::NodeVisitor + { + public: + SetFilterSettingsVisitor(osg::Texture::FilterMode minFilter, osg::Texture::FilterMode magFilter, int maxAnisotropy) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mMinFilter(minFilter) + , mMagFilter(magFilter) + , mMaxAnisotropy(maxAnisotropy) + { + } + + virtual void apply(osg::Node& node) + { + osg::StateSet* stateset = node.getStateSet(); + if (stateset) + applyStateSet(stateset); + + traverse(node); + } + + void applyStateSet(osg::StateSet* stateset) + { + const osg::StateSet::TextureAttributeList& texAttributes = stateset->getTextureAttributeList(); + for(unsigned int unit=0;unitgetTextureAttribute(unit, osg::StateAttribute::TEXTURE); + if (texture) + applyStateAttribute(texture); + } + } + + void applyStateAttribute(osg::StateAttribute* attr) + { + osg::Texture* tex = attr->asTexture(); + if (tex) + { + tex->setFilter(osg::Texture::MIN_FILTER, mMinFilter); + tex->setFilter(osg::Texture::MAG_FILTER, mMagFilter); + tex->setMaxAnisotropy(mMaxAnisotropy); + } + } + private: + osg::Texture::FilterMode mMinFilter; + osg::Texture::FilterMode mMagFilter; + int mMaxAnisotropy; + }; + + + + SceneManager::SceneManager(const VFS::Manager *vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) + : ResourceManager(vfs) + , mShaderManager(new Shader::ShaderManager) + , mForceShaders(false) + , mClampLighting(true) + , mForcePerPixelLighting(false) + , mAutoUseNormalMaps(false) + , mAutoUseSpecularMaps(false) + , mInstanceCache(new MultiObjectCache) + , mImageManager(imageManager) , mNifFileManager(nifFileManager) + , mMinFilter(osg::Texture::LINEAR_MIPMAP_LINEAR) + , mMagFilter(osg::Texture::LINEAR) + , mMaxAnisotropy(1) + , mUnRefImageDataAfterApply(false) , mParticleSystemMask(~0u) { } + void SceneManager::setForceShaders(bool force) + { + mForceShaders = force; + } + + bool SceneManager::getForceShaders() const + { + return mForceShaders; + } + + void SceneManager::recreateShaders(osg::ref_ptr node) + { + Shader::ShaderVisitor shaderVisitor(*mShaderManager.get(), *mImageManager, "objects_vertex.glsl", "objects_fragment.glsl"); + shaderVisitor.setForceShaders(mForceShaders); + shaderVisitor.setClampLighting(mClampLighting); + shaderVisitor.setForcePerPixelLighting(mForcePerPixelLighting); + shaderVisitor.setAllowedToModifyStateSets(false); + node->accept(shaderVisitor); + } + + void SceneManager::setClampLighting(bool clamp) + { + mClampLighting = clamp; + } + + bool SceneManager::getClampLighting() const + { + return mClampLighting; + } + + void SceneManager::setForcePerPixelLighting(bool force) + { + mForcePerPixelLighting = force; + } + + bool SceneManager::getForcePerPixelLighting() const + { + return mForcePerPixelLighting; + } + + void SceneManager::setAutoUseNormalMaps(bool use) + { + mAutoUseNormalMaps = use; + } + + void SceneManager::setNormalMapPattern(const std::string &pattern) + { + mNormalMapPattern = pattern; + } + + void SceneManager::setNormalHeightMapPattern(const std::string &pattern) + { + mNormalHeightMapPattern = pattern; + } + + void SceneManager::setAutoUseSpecularMaps(bool use) + { + mAutoUseSpecularMaps = use; + } + + void SceneManager::setSpecularMapPattern(const std::string &pattern) + { + mSpecularMapPattern = pattern; + } + SceneManager::~SceneManager() { // this has to be defined in the .cpp file as we can't delete incomplete types + } + Shader::ShaderManager &SceneManager::getShaderManager() + { + return *mShaderManager.get(); + } + + void SceneManager::setShaderPath(const std::string &path) + { + mShaderManager->setShaderPath(path); } /// @brief Callback to read image files from the VFS. class ImageReadCallback : public osgDB::ReadFileCallback { public: - ImageReadCallback(Resource::TextureManager* textureMgr) - : mTextureManager(textureMgr) + ImageReadCallback(Resource::ImageManager* imageMgr) + : mImageManager(imageMgr) { } @@ -132,7 +284,7 @@ namespace Resource { try { - return osgDB::ReaderWriter::ReadResult(mTextureManager->getImage(filename), osgDB::ReaderWriter::ReadResult::FILE_LOADED); + return osgDB::ReaderWriter::ReadResult(mImageManager->getImage(filename), osgDB::ReaderWriter::ReadResult::FILE_LOADED); } catch (std::exception& e) { @@ -141,7 +293,7 @@ namespace Resource } private: - Resource::TextureManager* mTextureManager; + Resource::ImageManager* mImageManager; }; std::string getFileExtension(const std::string& file) @@ -152,11 +304,11 @@ namespace Resource return std::string(); } - osg::ref_ptr load (Files::IStreamPtr file, const std::string& normalizedFilename, Resource::TextureManager* textureMgr, Resource::NifFileManager* nifFileManager) + osg::ref_ptr load (Files::IStreamPtr file, const std::string& normalizedFilename, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) { std::string ext = getFileExtension(normalizedFilename); if (ext == "nif") - return NifOsg::Loader::load(nifFileManager->get(normalizedFilename), textureMgr); + return NifOsg::Loader::load(nifFileManager->get(normalizedFilename), imageManager); else { osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(ext); @@ -171,7 +323,7 @@ namespace Resource // Set a ReadFileCallback so that image files referenced in the model are read from our virtual file system instead of the osgDB. // Note, for some formats (.obj/.mtl) that reference other (non-image) files a findFileCallback would be necessary. // but findFileCallback does not support virtual files, so we can't implement it. - options->setReadFileCallback(new ImageReadCallback(textureMgr)); + options->setReadFileCallback(new ImageReadCallback(imageManager)); osgDB::ReaderWriter::ReadResult result = reader->readNode(*file, options); if (!result.success()) @@ -189,46 +341,105 @@ namespace Resource std::string normalized = name; mVFS->normalizeFilename(normalized); - Index::iterator it = mIndex.find(normalized); - if (it == mIndex.end()) + osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); + if (obj) + return osg::ref_ptr(static_cast(obj.get())); + else { osg::ref_ptr loaded; try { Files::IStreamPtr file = mVFS->get(normalized); - loaded = load(file, normalized, mTextureManager, mNifFileManager); + loaded = load(file, normalized, mImageManager, mNifFileManager); } catch (std::exception& e) { - std::cerr << "Failed to load '" << name << "': " << e.what() << ", using marker_error.nif instead" << std::endl; - Files::IStreamPtr file = mVFS->get("meshes/marker_error.nif"); - normalized = "meshes/marker_error.nif"; - loaded = load(file, normalized, mTextureManager, mNifFileManager); + static const char * const sMeshTypes[] = { "nif", "osg", "osgt", "osgb", "osgx", "osg2" }; + + for (unsigned int i=0; iexists(normalized)) + { + std::cerr << "Failed to load '" << name << "': " << e.what() << ", using marker_error." << sMeshTypes[i] << " instead" << std::endl; + Files::IStreamPtr file = mVFS->get(normalized); + loaded = load(file, normalized, mImageManager, mNifFileManager); + break; + } + } + + if (!loaded) + throw; } + // set filtering settings + SetFilterSettingsVisitor setFilterSettingsVisitor(mMinFilter, mMagFilter, mMaxAnisotropy); + loaded->accept(setFilterSettingsVisitor); + SetFilterSettingsControllerVisitor setFilterSettingsControllerVisitor(mMinFilter, mMagFilter, mMaxAnisotropy); + loaded->accept(setFilterSettingsControllerVisitor); + + Shader::ShaderVisitor shaderVisitor(*mShaderManager.get(), *mImageManager, "objects_vertex.glsl", "objects_fragment.glsl"); + shaderVisitor.setForceShaders(mForceShaders); + shaderVisitor.setClampLighting(mClampLighting); + shaderVisitor.setForcePerPixelLighting(mForcePerPixelLighting); + shaderVisitor.setAutoUseNormalMaps(mAutoUseNormalMaps); + shaderVisitor.setNormalMapPattern(mNormalMapPattern); + shaderVisitor.setNormalHeightMapPattern(mNormalHeightMapPattern); + shaderVisitor.setAutoUseSpecularMaps(mAutoUseSpecularMaps); + shaderVisitor.setSpecularMapPattern(mSpecularMapPattern); + loaded->accept(shaderVisitor); + + // share state + mSharedStateMutex.lock(); osgDB::Registry::instance()->getOrCreateSharedStateManager()->share(loaded.get()); + mSharedStateMutex.unlock(); if (mIncrementalCompileOperation) mIncrementalCompileOperation->add(loaded); - mIndex[normalized] = loaded; + mCache->addEntryToObjectCache(normalized, loaded); return loaded; } - else - return it->second; } - osg::ref_ptr SceneManager::createInstance(const std::string &name) + osg::ref_ptr SceneManager::cacheInstance(const std::string &name) + { + std::string normalized = name; + mVFS->normalizeFilename(normalized); + + osg::ref_ptr node = createInstance(normalized); + mInstanceCache->addEntryToObjectCache(normalized, node.get()); + return node; + } + + osg::ref_ptr SceneManager::createInstance(const std::string& name) { osg::ref_ptr scene = getTemplate(name); osg::ref_ptr cloned = osg::clone(scene.get(), SceneUtil::CopyOp()); + + // add a ref to the original template, to hint to the cache that it's still being used and should be kept in cache + cloned->getOrCreateUserDataContainer()->addUserObject(const_cast(scene.get())); + return cloned; } - osg::ref_ptr SceneManager::createInstance(const std::string &name, osg::Group* parentNode) + osg::ref_ptr SceneManager::getInstance(const std::string &name) { - osg::ref_ptr cloned = createInstance(name); + std::string normalized = name; + mVFS->normalizeFilename(normalized); + + osg::ref_ptr obj = mInstanceCache->takeFromObjectCache(normalized); + if (obj.get()) + return static_cast(obj.get()); + + return createInstance(normalized); + + } + + osg::ref_ptr SceneManager::getInstance(const std::string &name, osg::Group* parentNode) + { + osg::ref_ptr cloned = getInstance(name); attachTo(cloned, parentNode); return cloned; } @@ -241,10 +452,8 @@ namespace Resource void SceneManager::releaseGLObjects(osg::State *state) { - for (Index::iterator it = mIndex.begin(); it != mIndex.end(); ++it) - { - it->second->releaseGLObjects(state); - } + mCache->releaseGLObjects(state); + mInstanceCache->releaseGLObjects(state); } void SceneManager::setIncrementalCompileOperation(osgUtil::IncrementalCompileOperation *ico) @@ -258,14 +467,9 @@ namespace Resource node->accept(visitor); } - const VFS::Manager* SceneManager::getVFS() const + Resource::ImageManager* SceneManager::getImageManager() { - return mVFS; - } - - Resource::TextureManager* SceneManager::getTextureManager() - { - return mTextureManager; + return mImageManager; } void SceneManager::setParticleSystemMask(unsigned int mask) @@ -273,4 +477,67 @@ namespace Resource mParticleSystemMask = mask; } + void SceneManager::setFilterSettings(const std::string &magfilter, const std::string &minfilter, + const std::string &mipmap, int maxAnisotropy) + { + osg::Texture::FilterMode min = osg::Texture::LINEAR; + osg::Texture::FilterMode mag = osg::Texture::LINEAR; + + if(magfilter == "nearest") + mag = osg::Texture::NEAREST; + else if(magfilter != "linear") + std::cerr<< "Invalid texture mag filter: "<accept(setFilterSettingsVisitor); + mCache->accept(setFilterSettingsControllerVisitor); + } + + void SceneManager::applyFilterSettings(osg::Texture *tex) + { + tex->setFilter(osg::Texture::MIN_FILTER, mMinFilter); + tex->setFilter(osg::Texture::MAG_FILTER, mMagFilter); + tex->setMaxAnisotropy(mMaxAnisotropy); + } + + void SceneManager::setUnRefImageDataAfterApply(bool unref) + { + mUnRefImageDataAfterApply = unref; + } + + void SceneManager::updateCache(double referenceTime) + { + ResourceManager::updateCache(referenceTime); + + mInstanceCache->removeUnreferencedObjectsInCache(); + } + } diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 67fa2ab433..a2107fc4ac 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -3,53 +3,102 @@ #include #include +#include #include #include +#include + +#include "resourcemanager.hpp" namespace Resource { - class TextureManager; + class ImageManager; class NifFileManager; } -namespace VFS -{ - class Manager; -} - namespace osgUtil { class IncrementalCompileOperation; } +namespace Shader +{ + class ShaderManager; +} + namespace Resource { - /// @brief Handles loading and caching of scenes, e.g. NIF files - class SceneManager + class MultiObjectCache; + + /// @brief Handles loading and caching of scenes, e.g. .nif files or .osg files + /// @note Some methods of the scene manager can be used from any thread, see the methods documentation for more details. + class SceneManager : public ResourceManager { public: - SceneManager(const VFS::Manager* vfs, Resource::TextureManager* textureManager, Resource::NifFileManager* nifFileManager); + SceneManager(const VFS::Manager* vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager); ~SceneManager(); + Shader::ShaderManager& getShaderManager(); + + /// Re-create shaders for this node, need to call this if texture stages or vertex color mode have changed. + void recreateShaders(osg::ref_ptr node); + + /// @see ShaderVisitor::setForceShaders + void setForceShaders(bool force); + bool getForceShaders() const; + + /// @see ShaderVisitor::setClampLighting + void setClampLighting(bool clamp); + bool getClampLighting() const; + + /// @see ShaderVisitor::setForcePerPixelLighting + void setForcePerPixelLighting(bool force); + bool getForcePerPixelLighting() const; + + /// @see ShaderVisitor::setAutoUseNormalMaps + void setAutoUseNormalMaps(bool use); + + /// @see ShaderVisitor::setNormalMapPattern + void setNormalMapPattern(const std::string& pattern); + + /// @see ShaderVisitor::setNormalHeightMapPattern + void setNormalHeightMapPattern(const std::string& pattern); + + void setAutoUseSpecularMaps(bool use); + + void setSpecularMapPattern(const std::string& pattern); + + void setShaderPath(const std::string& path); + /// Get a read-only copy of this scene "template" /// @note If the given filename does not exist or fails to load, an error marker mesh will be used instead. /// If even the error marker mesh can not be found, an exception is thrown. + /// @note Thread safe. osg::ref_ptr getTemplate(const std::string& name); - /// Create an instance of the given scene template - /// @see getTemplate - osg::ref_ptr createInstance(const std::string& name); + /// Create an instance of the given scene template and cache it for later use, so that future calls to getInstance() can simply + /// return this cached object instead of creating a new one. + /// @note The returned ref_ptr may be kept around by the caller to ensure that the object stays in cache for as long as needed. + /// @note Thread safe. + osg::ref_ptr cacheInstance(const std::string& name); - /// Create an instance of the given scene template and immediately attach it to a parent node + /// Get an instance of the given scene template /// @see getTemplate - osg::ref_ptr createInstance(const std::string& name, osg::Group* parentNode); + /// @note Thread safe. + osg::ref_ptr getInstance(const std::string& name); + + /// Get an instance of the given scene template and immediately attach it to a parent node + /// @see getTemplate + /// @note Not thread safe, unless parentNode is not part of the main scene graph yet. + osg::ref_ptr getInstance(const std::string& name, osg::Group* parentNode); /// Attach the given scene instance to the given parent node /// @note You should have the parentNode in its intended position before calling this method, /// so that world space particles of the \a instance get transformed correctly. /// @note Assumes the given instance was not attached to any parents before. + /// @note Not thread safe, unless parentNode is not part of the main scene graph yet. void attachTo(osg::Node* instance, osg::Group* parentNode) const; /// Manually release created OpenGL objects for the given graphics context. This may be required @@ -62,26 +111,56 @@ namespace Resource /// @note SceneManager::attachTo calls this method automatically, only needs to be called by users if manually attaching void notifyAttached(osg::Node* node) const; - const VFS::Manager* getVFS() const; - - Resource::TextureManager* getTextureManager(); + Resource::ImageManager* getImageManager(); /// @param mask The node mask to apply to loaded particle system nodes. void setParticleSystemMask(unsigned int mask); + /// @warning It is unsafe to call this method while the draw thread is using textures! call Viewer::stopThreading first. + void setFilterSettings(const std::string &magfilter, const std::string &minfilter, + const std::string &mipmap, int maxAnisotropy); + + /// Apply filter settings to the given texture. Note, when loading an object through this scene manager (i.e. calling getTemplate or createInstance) + /// the filter settings are applied automatically. This method is provided for textures that were created outside of the SceneManager. + void applyFilterSettings (osg::Texture* tex); + + /// Keep a copy of the texture data around in system memory? This is needed when using multiple graphics contexts, + /// otherwise should be disabled to reduce memory usage. + void setUnRefImageDataAfterApply(bool unref); + + /// @see ResourceManager::updateCache + virtual void updateCache(double referenceTime); + private: - const VFS::Manager* mVFS; - Resource::TextureManager* mTextureManager; + + osg::ref_ptr createInstance(const std::string& name); + + std::auto_ptr mShaderManager; + bool mForceShaders; + bool mClampLighting; + bool mForcePerPixelLighting; + bool mAutoUseNormalMaps; + std::string mNormalMapPattern; + std::string mNormalHeightMapPattern; + bool mAutoUseSpecularMaps; + std::string mSpecularMapPattern; + + osg::ref_ptr mInstanceCache; + + OpenThreads::Mutex mSharedStateMutex; + + Resource::ImageManager* mImageManager; Resource::NifFileManager* mNifFileManager; + osg::Texture::FilterMode mMinFilter; + osg::Texture::FilterMode mMagFilter; + int mMaxAnisotropy; + bool mUnRefImageDataAfterApply; + osg::ref_ptr mIncrementalCompileOperation; unsigned int mParticleSystemMask; - // observer_ptr? - typedef std::map > Index; - Index mIndex; - SceneManager(const SceneManager&); void operator = (const SceneManager&); }; diff --git a/components/resource/texturemanager.cpp b/components/resource/texturemanager.cpp deleted file mode 100644 index d7f3fc61a1..0000000000 --- a/components/resource/texturemanager.cpp +++ /dev/null @@ -1,314 +0,0 @@ -#include "texturemanager.hpp" - -#include -#include -#include -#include - -#include - -#include - -#ifdef OSG_LIBRARY_STATIC -// This list of plugins should match with the list in the top-level CMakelists.txt. -USE_OSGPLUGIN(png) -USE_OSGPLUGIN(tga) -USE_OSGPLUGIN(dds) -USE_OSGPLUGIN(jpeg) -#endif - -namespace -{ - - osg::ref_ptr createWarningTexture() - { - osg::ref_ptr warningImage = new osg::Image; - - int width = 8, height = 8; - warningImage->allocateImage(width, height, 1, GL_RGB, GL_UNSIGNED_BYTE); - assert (warningImage->isDataContiguous()); - unsigned char* data = warningImage->data(); - for (int i=0;i warningTexture = new osg::Texture2D; - warningTexture->setImage(warningImage); - return warningTexture; - } - -} - -namespace Resource -{ - - TextureManager::TextureManager(const VFS::Manager *vfs) - : mVFS(vfs) - , mMinFilter(osg::Texture::LINEAR_MIPMAP_LINEAR) - , mMagFilter(osg::Texture::LINEAR) - , mMaxAnisotropy(1) - , mWarningTexture(createWarningTexture()) - , mUnRefImageDataAfterApply(false) - { - - } - - TextureManager::~TextureManager() - { - - } - - void TextureManager::setUnRefImageDataAfterApply(bool unref) - { - mUnRefImageDataAfterApply = unref; - } - - void TextureManager::setFilterSettings(const std::string &magfilter, const std::string &minfilter, - const std::string &mipmap, int maxAnisotropy, - osgViewer::Viewer *viewer) - { - osg::Texture::FilterMode min = osg::Texture::LINEAR; - osg::Texture::FilterMode mag = osg::Texture::LINEAR; - - if(magfilter == "nearest") - mag = osg::Texture::NEAREST; - else if(magfilter != "linear") - std::cerr<< "Invalid texture mag filter: "<stopThreading(); - setFilterSettings(min, mag, maxAnisotropy); - if(viewer) viewer->startThreading(); - } - - void TextureManager::setFilterSettings(osg::Texture::FilterMode minFilter, osg::Texture::FilterMode magFilter, int maxAnisotropy) - { - mMinFilter = minFilter; - mMagFilter = magFilter; - mMaxAnisotropy = std::max(1, maxAnisotropy); - - for (std::map >::iterator it = mTextures.begin(); it != mTextures.end(); ++it) - { - osg::ref_ptr tex = it->second; - - // Keep mip-mapping disabled if the texture creator explicitely requested no mipmapping. - osg::Texture::FilterMode oldMin = tex->getFilter(osg::Texture::MIN_FILTER); - if (oldMin == osg::Texture::LINEAR || oldMin == osg::Texture::NEAREST) - { - osg::Texture::FilterMode newMin = osg::Texture::LINEAR; - switch (mMinFilter) - { - case osg::Texture::LINEAR: - case osg::Texture::LINEAR_MIPMAP_LINEAR: - case osg::Texture::LINEAR_MIPMAP_NEAREST: - newMin = osg::Texture::LINEAR; - break; - case osg::Texture::NEAREST: - case osg::Texture::NEAREST_MIPMAP_LINEAR: - case osg::Texture::NEAREST_MIPMAP_NEAREST: - newMin = osg::Texture::NEAREST; - break; - } - tex->setFilter(osg::Texture::MIN_FILTER, newMin); - } - else - tex->setFilter(osg::Texture::MIN_FILTER, mMinFilter); - - tex->setFilter(osg::Texture::MAG_FILTER, mMagFilter); - tex->setMaxAnisotropy(static_cast(mMaxAnisotropy)); - } - } - - /* - osg::ref_ptr TextureManager::getImage(const std::string &filename) - { - - } - */ - - bool checkSupported(osg::Image* image, const std::string& filename) - { - switch(image->getPixelFormat()) - { - case(GL_COMPRESSED_RGB_S3TC_DXT1_EXT): - case(GL_COMPRESSED_RGBA_S3TC_DXT1_EXT): - case(GL_COMPRESSED_RGBA_S3TC_DXT3_EXT): - case(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT): - { -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) - osg::GLExtensions* exts = osg::GLExtensions::Get(0, false); - if (exts && !exts->isTextureCompressionS3TCSupported - // This one works too. Should it be included in isTextureCompressionS3TCSupported()? Submitted as a patch to OSG. - && !osg::isGLExtensionSupported(0, "GL_S3_s3tc")) -#else - osg::Texture::Extensions* exts = osg::Texture::getExtensions(0, false); - if (exts && !exts->isTextureCompressionS3TCSupported() - // This one works too. Should it be included in isTextureCompressionS3TCSupported()? Submitted as a patch to OSG. - && !osg::isGLExtensionSupported(0, "GL_S3_s3tc")) -#endif - { - std::cerr << "Error loading " << filename << ": no S3TC texture compression support installed" << std::endl; - return false; - } - break; - } - // not bothering with checks for other compression formats right now, we are unlikely to ever use those anyway - default: - return true; - } - return true; - } - - osg::ref_ptr TextureManager::getImage(const std::string &filename) - { - std::string normalized = filename; - mVFS->normalizeFilename(normalized); - std::map >::iterator found = mImages.find(normalized); - if (found != mImages.end()) - return found->second; - else - { - Files::IStreamPtr stream; - try - { - stream = mVFS->get(normalized.c_str()); - } - catch (std::exception& e) - { - std::cerr << "Failed to open image: " << e.what() << std::endl; - return NULL; - } - - osg::ref_ptr opts (new osgDB::Options); - opts->setOptionString("dds_dxt1_detect_rgba"); // tx_creature_werewolf.dds isn't loading in the correct format without this option - size_t extPos = normalized.find_last_of('.'); - std::string ext; - if (extPos != std::string::npos && extPos+1 < normalized.size()) - ext = normalized.substr(extPos+1); - osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(ext); - if (!reader) - { - std::cerr << "Error loading " << filename << ": no readerwriter for '" << ext << "' found" << std::endl; - return NULL; - } - - osgDB::ReaderWriter::ReadResult result = reader->readImage(*stream, opts); - if (!result.success()) - { - std::cerr << "Error loading " << filename << ": " << result.message() << " code " << result.status() << std::endl; - return NULL; - } - - osg::Image* image = result.getImage(); - if (!checkSupported(image, filename)) - { - return NULL; - } - - mImages.insert(std::make_pair(normalized, image)); - return image; - } - } - - osg::ref_ptr TextureManager::getTexture2D(const std::string &filename, osg::Texture::WrapMode wrapS, osg::Texture::WrapMode wrapT) - { - std::string normalized = filename; - mVFS->normalizeFilename(normalized); - MapKey key = std::make_pair(std::make_pair(wrapS, wrapT), normalized); - std::map >::iterator found = mTextures.find(key); - if (found != mTextures.end()) - { - return found->second; - } - else - { - Files::IStreamPtr stream; - try - { - stream = mVFS->get(normalized.c_str()); - } - catch (std::exception& e) - { - std::cerr << "Failed to open texture: " << e.what() << std::endl; - return mWarningTexture; - } - - osg::ref_ptr opts (new osgDB::Options); - opts->setOptionString("dds_dxt1_detect_rgba"); // tx_creature_werewolf.dds isn't loading in the correct format without this option - size_t extPos = normalized.find_last_of('.'); - std::string ext; - if (extPos != std::string::npos && extPos+1 < normalized.size()) - ext = normalized.substr(extPos+1); - osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(ext); - if (!reader) - { - std::cerr << "Error loading " << filename << ": no readerwriter for '" << ext << "' found" << std::endl; - return mWarningTexture; - } - - osgDB::ReaderWriter::ReadResult result = reader->readImage(*stream, opts); - if (!result.success()) - { - std::cerr << "Error loading " << filename << ": " << result.message() << " code " << result.status() << std::endl; - return mWarningTexture; - } - - osg::Image* image = result.getImage(); - if (!checkSupported(image, filename)) - { - return mWarningTexture; - } - - // We need to flip images, because the Morrowind texture coordinates use the DirectX convention (top-left image origin), - // but OpenGL uses bottom left as the image origin. - // For some reason this doesn't concern DDS textures, which are already flipped when loaded. - if (ext != "dds") - { - image->flipVertical(); - } - - osg::ref_ptr texture(new osg::Texture2D); - texture->setImage(image); - texture->setWrap(osg::Texture::WRAP_S, wrapS); - texture->setWrap(osg::Texture::WRAP_T, wrapT); - texture->setFilter(osg::Texture::MIN_FILTER, mMinFilter); - texture->setFilter(osg::Texture::MAG_FILTER, mMagFilter); - texture->setMaxAnisotropy(mMaxAnisotropy); - - texture->setUnRefImageDataAfterApply(mUnRefImageDataAfterApply); - - mTextures.insert(std::make_pair(key, texture)); - return texture; - } - } - - osg::Texture2D* TextureManager::getWarningTexture() - { - return mWarningTexture.get(); - } - -} diff --git a/components/resource/texturemanager.hpp b/components/resource/texturemanager.hpp deleted file mode 100644 index e12dfa0900..0000000000 --- a/components/resource/texturemanager.hpp +++ /dev/null @@ -1,76 +0,0 @@ -#ifndef OPENMW_COMPONENTS_RESOURCE_TEXTUREMANAGER_H -#define OPENMW_COMPONENTS_RESOURCE_TEXTUREMANAGER_H - -#include -#include - -#include -#include -#include - -namespace osgViewer -{ - class Viewer; -} - -namespace VFS -{ - class Manager; -} - -namespace Resource -{ - - /// @brief Handles loading/caching of Images and Texture StateAttributes. - class TextureManager - { - public: - TextureManager(const VFS::Manager* vfs); - ~TextureManager(); - - void setFilterSettings(const std::string &magfilter, const std::string &minfilter, - const std::string &mipmap, int maxAnisotropy, - osgViewer::Viewer *view); - - /// Keep a copy of the texture data around in system memory? This is needed when using multiple graphics contexts, - /// otherwise should be disabled to reduce memory usage. - void setUnRefImageDataAfterApply(bool unref); - - /// Create or retrieve a Texture2D using the specified image filename, and wrap parameters. - /// Returns the dummy texture if the given texture is not found. - osg::ref_ptr getTexture2D(const std::string& filename, osg::Texture::WrapMode wrapS, osg::Texture::WrapMode wrapT); - - /// Create or retrieve an Image - osg::ref_ptr getImage(const std::string& filename); - - const VFS::Manager* getVFS() { return mVFS; } - - osg::Texture2D* getWarningTexture(); - - private: - const VFS::Manager* mVFS; - - osg::Texture::FilterMode mMinFilter; - osg::Texture::FilterMode mMagFilter; - int mMaxAnisotropy; - - typedef std::pair, std::string> MapKey; - - std::map > mImages; - - std::map > mTextures; - - osg::ref_ptr mWarningTexture; - - bool mUnRefImageDataAfterApply; - - /// @warning It is unsafe to call this function when a draw thread is using the textures. Call stopThreading() first! - void setFilterSettings(osg::Texture::FilterMode minFilter, osg::Texture::FilterMode maxFilter, int maxAnisotropy); - - TextureManager(const TextureManager&); - void operator = (const TextureManager&); - }; - -} - -#endif diff --git a/components/sceneutil/attach.cpp b/components/sceneutil/attach.cpp index d8cfa428a2..c80b9be365 100644 --- a/components/sceneutil/attach.cpp +++ b/components/sceneutil/attach.cpp @@ -1,10 +1,10 @@ #include "attach.hpp" #include +#include #include #include -#include #include #include #include @@ -34,14 +34,30 @@ namespace SceneUtil std::string lowerName = Misc::StringUtils::lowerCase(node.getName()); if ((lowerName.size() >= mFilter.size() && lowerName.compare(0, mFilter.size(), mFilter) == 0) || (lowerName.size() >= mFilter2.size() && lowerName.compare(0, mFilter2.size(), mFilter2) == 0)) - { - mParent->addChild(&node); - } + mToCopy.push_back(&node); else traverse(node); } + void doCopy() + { + for (std::vector >::iterator it = mToCopy.begin(); it != mToCopy.end(); ++it) + { + osg::ref_ptr node = *it; + if (node->getNumParents() > 1) + std::cerr << "CopyRigVisitor warning: node has multiple parents" << std::endl; + while (node->getNumParents()) + node->getParent(0)->removeChild(node); + + mParent->addChild(node); + } + mToCopy.clear(); + } + private: + typedef std::vector > NodeVector; + NodeVector mToCopy; + osg::ref_ptr mParent; std::string mFilter; std::string mFilter2; @@ -55,10 +71,20 @@ namespace SceneUtil CopyRigVisitor copyVisitor(handle, filter); toAttach->accept(copyVisitor); + copyVisitor.doCopy(); - master->asGroup()->addChild(handle); - - return handle; + if (handle->getNumChildren() == 1) + { + osg::ref_ptr newHandle = handle->getChild(0); + handle->removeChild(newHandle); + master->asGroup()->addChild(newHandle); + return newHandle; + } + else + { + master->asGroup()->addChild(handle); + return handle; + } } else { @@ -93,9 +119,15 @@ namespace SceneUtil // Need to invert culling because of the negative scale // Note: for absolute correctness we would need to check the current front face for every mesh then invert it // However MW isn't doing this either, so don't. Assuming all meshes are using backface culling is more efficient. - osg::FrontFace* frontFace = new osg::FrontFace; - frontFace->setMode(osg::FrontFace::CLOCKWISE); - trans->getOrCreateStateSet()->setAttributeAndModes(frontFace, osg::StateAttribute::ON); + static osg::ref_ptr frontFaceStateSet; + if (!frontFaceStateSet) + { + frontFaceStateSet = new osg::StateSet; + osg::FrontFace* frontFace = new osg::FrontFace; + frontFace->setMode(osg::FrontFace::CLOCKWISE); + frontFaceStateSet->setAttributeAndModes(frontFace, osg::StateAttribute::ON); + } + trans->setStateSet(frontFaceStateSet); } if (trans) diff --git a/components/sceneutil/clone.cpp b/components/sceneutil/clone.cpp index a372b1ebd8..26f3f5c7cd 100644 --- a/components/sceneutil/clone.cpp +++ b/components/sceneutil/clone.cpp @@ -81,8 +81,6 @@ namespace SceneUtil #endif osg::Drawable* cloned = osg::clone(drawable, copyop); - if (cloned->getUpdateCallback()) - cloned->setUpdateCallback(osg::clone(cloned->getUpdateCallback(), *this)); return cloned; } if (dynamic_cast(drawable)) diff --git a/components/sceneutil/controller.cpp b/components/sceneutil/controller.cpp index 7762b48d07..12c89c87d0 100644 --- a/components/sceneutil/controller.cpp +++ b/components/sceneutil/controller.cpp @@ -3,9 +3,7 @@ #include "statesetupdater.hpp" #include -#include #include -#include namespace SceneUtil { @@ -65,11 +63,7 @@ namespace SceneUtil void ControllerVisitor::apply(osg::Node &node) { -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) osg::Callback* callback = node.getUpdateCallback(); -#else - osg::NodeCallback* callback = node.getUpdateCallback(); -#endif while (callback) { if (Controller* ctrl = dynamic_cast(callback)) @@ -90,25 +84,6 @@ namespace SceneUtil traverse(node); } - void ControllerVisitor::apply(osg::Geode &geode) - { - for (unsigned int i=0; igetUpdateCallback(); -#else - osg::Drawable::UpdateCallback* callback = drw->getUpdateCallback(); -#endif - - if (Controller* ctrl = dynamic_cast(callback)) - visit(geode, *ctrl); - } - - apply(static_cast(geode)); - } - AssignControllerSourcesVisitor::AssignControllerSourcesVisitor() : ControllerVisitor() { diff --git a/components/sceneutil/controller.hpp b/components/sceneutil/controller.hpp index 7399ecad58..3f1ec874eb 100644 --- a/components/sceneutil/controller.hpp +++ b/components/sceneutil/controller.hpp @@ -63,7 +63,6 @@ namespace SceneUtil ControllerVisitor(); virtual void apply(osg::Node& node); - virtual void apply(osg::Geode& geode); virtual void visit(osg::Node& node, Controller& ctrl) = 0; }; diff --git a/components/sceneutil/lightcontroller.cpp b/components/sceneutil/lightcontroller.cpp index 511937a282..e3ea93843c 100644 --- a/components/sceneutil/lightcontroller.cpp +++ b/components/sceneutil/lightcontroller.cpp @@ -125,7 +125,7 @@ namespace SceneUtil traverse(node, nv); } - void LightController::setDiffuse(osg::Vec4f color) + void LightController::setDiffuse(const osg::Vec4f& color) { mDiffuseColor = color; } diff --git a/components/sceneutil/lightcontroller.hpp b/components/sceneutil/lightcontroller.hpp index f6e2fa9faf..8f70af343d 100644 --- a/components/sceneutil/lightcontroller.hpp +++ b/components/sceneutil/lightcontroller.hpp @@ -24,7 +24,7 @@ namespace SceneUtil void setType(LightType type); - void setDiffuse(osg::Vec4f color); + void setDiffuse(const osg::Vec4f& color); virtual void operator()(osg::Node* node, osg::NodeVisitor* nv); diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 1706bb2b1e..f7c0573a47 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -3,7 +3,6 @@ #include #include -#include #include @@ -45,19 +44,34 @@ namespace SceneUtil virtual void apply(osg::State& state) const { + if (mLights.empty()) + return; osg::Matrix modelViewMatrix = state.getModelViewMatrix(); state.applyModelViewMatrix(state.getInitialViewMatrix()); for (unsigned int i=0; isetLightNum(i+mIndex); - mLights[i]->apply(state); - } + applyLight((GLenum)((int)GL_LIGHT0 + i + mIndex), mLights[i].get()); state.applyModelViewMatrix(modelViewMatrix); } + void applyLight(GLenum lightNum, const osg::Light* light) const + { + glLightfv( lightNum, GL_AMBIENT, light->getAmbient().ptr() ); + glLightfv( lightNum, GL_DIFFUSE, light->getDiffuse().ptr() ); + glLightfv( lightNum, GL_SPECULAR, light->getSpecular().ptr() ); + glLightfv( lightNum, GL_POSITION, light->getPosition().ptr() ); + // TODO: enable this once spot lights are supported + // need to transform SPOT_DIRECTION by the world matrix? + //glLightfv( lightNum, GL_SPOT_DIRECTION, light->getDirection().ptr() ); + //glLightf ( lightNum, GL_SPOT_EXPONENT, light->getSpotExponent() ); + //glLightf ( lightNum, GL_SPOT_CUTOFF, light->getSpotCutoff() ); + glLightf ( lightNum, GL_CONSTANT_ATTENUATION, light->getConstantAttenuation() ); + glLightf ( lightNum, GL_LINEAR_ATTENUATION, light->getLinearAttenuation() ); + glLightf ( lightNum, GL_QUADRATIC_ATTENUATION, light->getQuadraticAttenuation() ); + } + private: unsigned int mIndex; @@ -192,19 +206,28 @@ namespace SceneUtil return found->second; else { - + osg::ref_ptr stateset = new osg::StateSet; std::vector > lights; for (unsigned int i=0; imLightSource->getLight(frameNum)); + } + // the first light state attribute handles the actual state setting for all lights + // it's best to batch these up so that we don't need to touch the modelView matrix more than necessary osg::ref_ptr attr = new LightStateAttribute(mStartLight, lights); - - osg::ref_ptr stateset = new osg::StateSet; - // don't use setAttributeAndModes, that does not support light indices! stateset->setAttribute(attr, osg::StateAttribute::ON); stateset->setAssociatedModes(attr, osg::StateAttribute::ON); + // need to push some dummy attributes to ensure proper state tracking + // lights need to reset to their default when the StateSet is popped + for (unsigned int i=1; i dummy = new LightStateAttribute(mStartLight+i, std::vector >()); + stateset->setAttribute(dummy, osg::StateAttribute::ON); + } + stateSetCache.insert(std::make_pair(hash, stateset)); return stateset; } @@ -242,6 +265,17 @@ namespace SceneUtil void LightManager::setStartLight(int start) { mStartLight = start; + + // Set default light state to zero + for (int i=start; i<8; ++i) + { + osg::ref_ptr defaultLight (new osg::Light(i)); + defaultLight->setAmbient(osg::Vec4()); + defaultLight->setDiffuse(osg::Vec4()); + defaultLight->setSpecular(osg::Vec4()); + defaultLight->setConstantAttenuation(0.f); + getOrCreateStateSet()->setAttributeAndModes(defaultLight, osg::StateAttribute::OFF); + } } int LightManager::getStartLight() const @@ -309,8 +343,15 @@ namespace SceneUtil const osg::RefMatrix* viewMatrix = cv->getCurrentRenderStage()->getInitialViewMatrix(); const std::vector& lights = mLightManager->getLightsInViewSpace(cv->getCurrentCamera(), viewMatrix); - // we do the intersections in view space - osg::BoundingSphere nodeBound = node->getBound(); + // get the node bounds in view space + // NB do not node->getBound() * modelView, that would apply the node's transformation twice + osg::BoundingSphere nodeBound; + osg::Group* group = node->asGroup(); + if (group) + { + for (unsigned int i=0; igetNumChildren(); ++i) + nodeBound.expandBy(group->getChild(i)->getBound()); + } osg::Matrixf mat = *cv->getModelViewMatrix(); transformBoundingSphere(mat, nodeBound); @@ -322,7 +363,7 @@ namespace SceneUtil mLightList.push_back(&l); } } - if (mLightList.size()) + if (!mLightList.empty()) { unsigned int maxLights = static_cast (8 - mLightManager->getStartLight()); @@ -371,28 +412,4 @@ namespace SceneUtil traverse(node, nv); } - void configureLight(osg::Light *light, float radius, bool isExterior, bool outQuadInLin, bool useQuadratic, - float quadraticValue, float quadraticRadiusMult, bool useLinear, float linearRadiusMult, float linearValue) - { - bool quadratic = useQuadratic && (!outQuadInLin || isExterior); - - float quadraticAttenuation = 0; - float linearAttenuation = 0; - if (quadratic) - { - float r = radius * quadraticRadiusMult; - quadraticAttenuation = quadraticValue / std::pow(r, 2); - } - if (useLinear) - { - float r = radius * linearRadiusMult; - linearAttenuation = linearValue / r; - } - - light->setLinearAttenuation(linearAttenuation); - light->setQuadraticAttenuation(quadraticAttenuation); - light->setConstantAttenuation(0.f); - - } - } diff --git a/components/sceneutil/lightmanager.hpp b/components/sceneutil/lightmanager.hpp index 3e6d3251bb..0d8610eadb 100644 --- a/components/sceneutil/lightmanager.hpp +++ b/components/sceneutil/lightmanager.hpp @@ -5,6 +5,7 @@ #include #include +#include namespace SceneUtil { @@ -168,10 +169,6 @@ namespace SceneUtil LightManager::LightList mLightList; }; - /// @brief Configures a light's attenuation according to vanilla Morrowind attenuation settings. - void configureLight(osg::Light* light, float radius, bool isExterior, bool outQuadInLin, bool useQuadratic, float quadraticValue, - float quadraticRadiusMult, bool useLinear, float linearRadiusMult, float linearValue); - } #endif diff --git a/components/sceneutil/lightutil.cpp b/components/sceneutil/lightutil.cpp new file mode 100644 index 0000000000..6499c54b1d --- /dev/null +++ b/components/sceneutil/lightutil.cpp @@ -0,0 +1,109 @@ +#include "lightutil.hpp" + +#include +#include +#include + +#include + +#include "lightmanager.hpp" +#include "lightcontroller.hpp" +#include "util.hpp" +#include "visitor.hpp" +#include "positionattitudetransform.hpp" + +namespace SceneUtil +{ + + void configureLight(osg::Light *light, float radius, bool isExterior, bool outQuadInLin, bool useQuadratic, + float quadraticValue, float quadraticRadiusMult, bool useLinear, float linearRadiusMult, float linearValue) + { + bool quadratic = useQuadratic && (!outQuadInLin || isExterior); + + float quadraticAttenuation = 0; + float linearAttenuation = 0; + if (quadratic) + { + float r = radius * quadraticRadiusMult; + quadraticAttenuation = quadraticValue / std::pow(r, 2); + } + if (useLinear) + { + float r = radius * linearRadiusMult; + linearAttenuation = linearValue / r; + } + + light->setLinearAttenuation(linearAttenuation); + light->setQuadraticAttenuation(quadraticAttenuation); + light->setConstantAttenuation(0.f); + + } + + void addLight (osg::Group* node, const ESM::Light* esmLight, unsigned int partsysMask, unsigned int lightMask, bool isExterior, bool outQuadInLin, bool useQuadratic, + float quadraticValue, float quadraticRadiusMult, bool useLinear, float linearRadiusMult, + float linearValue) + { + SceneUtil::FindByNameVisitor visitor("AttachLight"); + node->accept(visitor); + + osg::Group* attachTo = NULL; + if (visitor.mFoundNode) + { + attachTo = visitor.mFoundNode; + } + else + { + osg::ComputeBoundsVisitor computeBound; + computeBound.setTraversalMask(~partsysMask); + // We want the bounds of all children of the node, ignoring the node's local transformation + // So do a traverse(), not accept() + computeBound.traverse(*node); + + // PositionAttitudeTransform seems to be slightly faster than MatrixTransform + osg::ref_ptr trans(new SceneUtil::PositionAttitudeTransform); + trans->setPosition(computeBound.getBoundingBox().center()); + + node->addChild(trans); + + attachTo = trans; + } + + osg::ref_ptr lightSource (new SceneUtil::LightSource); + osg::ref_ptr light (new osg::Light); + lightSource->setNodeMask(lightMask); + + float radius = esmLight->mData.mRadius; + lightSource->setRadius(radius); + + configureLight(light, radius, isExterior, outQuadInLin, useQuadratic, quadraticValue, + quadraticRadiusMult, useLinear, linearRadiusMult, linearValue); + + osg::Vec4f diffuse = SceneUtil::colourFromRGB(esmLight->mData.mColor); + if (esmLight->mData.mFlags & ESM::Light::Negative) + { + diffuse *= -1; + diffuse.a() = 1; + } + light->setDiffuse(diffuse); + light->setAmbient(osg::Vec4f(0,0,0,1)); + light->setSpecular(osg::Vec4f(0,0,0,0)); + + lightSource->setLight(light); + + osg::ref_ptr ctrl (new SceneUtil::LightController); + ctrl->setDiffuse(light->getDiffuse()); + if (esmLight->mData.mFlags & ESM::Light::Flicker) + ctrl->setType(SceneUtil::LightController::LT_Flicker); + if (esmLight->mData.mFlags & ESM::Light::FlickerSlow) + ctrl->setType(SceneUtil::LightController::LT_FlickerSlow); + if (esmLight->mData.mFlags & ESM::Light::Pulse) + ctrl->setType(SceneUtil::LightController::LT_Pulse); + if (esmLight->mData.mFlags & ESM::Light::PulseSlow) + ctrl->setType(SceneUtil::LightController::LT_PulseSlow); + + lightSource->addUpdateCallback(ctrl); + + attachTo->addChild(lightSource); + } + +} diff --git a/components/sceneutil/lightutil.hpp b/components/sceneutil/lightutil.hpp new file mode 100644 index 0000000000..d6c9703409 --- /dev/null +++ b/components/sceneutil/lightutil.hpp @@ -0,0 +1,32 @@ +#ifndef OPENMW_COMPONENTS_LIGHTUTIL_H +#define OPENMW_COMPONENTS_LIGHTUTIL_H + +namespace osg +{ + class Group; +} + +namespace ESM +{ + struct Light; +} + +namespace SceneUtil +{ + + /// @brief Convert an ESM::Light to a SceneUtil::LightSource, and add it to a sub graph. + /// @note If the sub graph contains a node named "AttachLight" (case insensitive), then the light is added to that. + /// Otherwise, the light is added in the center of the node's bounds. + /// @param node The sub graph to add a light to + /// @param esmLight The light definition coming from the game files containing radius, color, flicker, etc. + /// @param partsysMask Node mask to ignore when computing the sub graph's bounding box. + /// @param lightMask Mask to assign to the newly created LightSource. + /// @param isExterior Is the light outside? May be used for deciding which attenuation settings to use. + /// @par Attenuation parameters come from the game INI file. + void addLight (osg::Group* node, const ESM::Light* esmLight, unsigned int partsysMask, unsigned int lightMask, bool isExterior, bool outQuadInLin, bool useQuadratic, + float quadraticValue, float quadraticRadiusMult, bool useLinear, float linearRadiusMult, + float linearValue); + +} + +#endif diff --git a/components/sceneutil/pathgridutil.cpp b/components/sceneutil/pathgridutil.cpp new file mode 100644 index 0000000000..1497beb2b8 --- /dev/null +++ b/components/sceneutil/pathgridutil.cpp @@ -0,0 +1,233 @@ +#include "pathgridutil.hpp" + +#include + +#include +#include + +namespace SceneUtil +{ + const unsigned short DiamondVertexCount = 6; + const unsigned short DiamondIndexCount = 24; + const unsigned short DiamondWireframeIndexCount = 24; + + const unsigned short DiamondConnectorVertexCount = 4; + + const unsigned short DiamondTotalVertexCount = DiamondVertexCount + DiamondConnectorVertexCount; + + const float DiamondWireframeScalar = 1.1f; + + const osg::Vec3f DiamondPoints[DiamondVertexCount] = + { + osg::Vec3f( 0.f, 0.f, DiamondHalfHeight * 2.f), + osg::Vec3f(-DiamondHalfWidth, -DiamondHalfWidth, DiamondHalfHeight), + osg::Vec3f(-DiamondHalfWidth, DiamondHalfWidth, DiamondHalfHeight), + osg::Vec3f( DiamondHalfWidth, -DiamondHalfWidth, DiamondHalfHeight), + osg::Vec3f( DiamondHalfWidth, DiamondHalfWidth, DiamondHalfHeight), + osg::Vec3f( 0.f, 0.f, 0.f) + }; + + const unsigned short DiamondIndices[DiamondIndexCount] = + { + 0, 2, 1, + 0, 1, 3, + 0, 3, 4, + 0, 4, 2, + 5, 1, 2, + 5, 3, 1, + 5, 4, 3, + 5, 2, 4 + }; + + const unsigned short DiamondWireframeIndices[DiamondWireframeIndexCount] = + { + 0, 1, + 0, 2, + 0, 3, + 0, 4, + 1, 2, + 2, 4, + 4, 3, + 3, 1, + 5, 1, + 5, 2, + 5, 3, + 5, 4 + }; + + const unsigned short DiamondConnectorVertices[DiamondConnectorVertexCount] = + { + 1, 2, 3, 4 + }; + + const osg::Vec4f DiamondColors[DiamondVertexCount] = + { + osg::Vec4f(0.f, 0.f, 1.f, 1.f), + osg::Vec4f(0.f, .05f, .95f, 1.f), + osg::Vec4f(0.f, .1f, .95f, 1.f), + osg::Vec4f(0.f, .15f, .95f, 1.f), + osg::Vec4f(0.f, .2f, .95f, 1.f), + osg::Vec4f(0.f, .25f, 9.f, 1.f) + }; + + const osg::Vec4f DiamondEdgeColor = osg::Vec4f(0.5f, 1.f, 1.f, 1.f); + const osg::Vec4f DiamondWireColor = osg::Vec4f(0.72f, 0.f, 0.96f, 1.f); + const osg::Vec4f DiamondFocusWireColor = osg::Vec4f(0.91f, 0.66f, 1.f, 1.f); + + osg::ref_ptr createPathgridGeometry(const ESM::Pathgrid& pathgrid) + { + const unsigned short PointCount = static_cast(pathgrid.mPoints.size()); + const size_t EdgeCount = pathgrid.mEdges.size(); + + const unsigned short VertexCount = PointCount * DiamondTotalVertexCount; + const unsigned short ColorCount = VertexCount; + const size_t PointIndexCount = PointCount * DiamondIndexCount; + const size_t EdgeIndexCount = EdgeCount * 2; + + osg::ref_ptr gridGeometry = new osg::Geometry(); + + osg::ref_ptr vertices = new osg::Vec3Array(VertexCount); + osg::ref_ptr colors = new osg::Vec4Array(ColorCount); + osg::ref_ptr pointIndices = + new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, PointIndexCount); + osg::ref_ptr lineIndices = + new osg::DrawElementsUShort(osg::PrimitiveSet::LINES, EdgeIndexCount); + + // Add each point/node + for (unsigned short pointIndex = 0; pointIndex < PointCount; ++pointIndex) + { + const ESM::Pathgrid::Point& point = pathgrid.mPoints[pointIndex]; + osg::Vec3f position = osg::Vec3f(point.mX, point.mY, point.mZ); + + unsigned short vertexOffset = pointIndex * DiamondTotalVertexCount; + unsigned short indexOffset = pointIndex * DiamondIndexCount; + + // Point + for (unsigned short i = 0; i < DiamondVertexCount; ++i) + { + (*vertices)[vertexOffset + i] = position + DiamondPoints[i]; + (*colors)[vertexOffset + i] = DiamondColors[i]; + } + + for (unsigned short i = 0; i < DiamondIndexCount; ++i) + { + pointIndices->setElement(indexOffset + i, vertexOffset + DiamondIndices[i]); + } + + // Connectors + vertexOffset += DiamondVertexCount; + for (unsigned short i = 0; i < DiamondConnectorVertexCount; ++i) + { + (*vertices)[vertexOffset + i] = position + DiamondPoints[DiamondConnectorVertices[i]]; + (*colors)[vertexOffset + i] = DiamondEdgeColor; + } + } + + // Add edges + unsigned short lineIndex = 0; + + for (ESM::Pathgrid::EdgeList::const_iterator edge = pathgrid.mEdges.begin(); + edge != pathgrid.mEdges.end(); ++edge) + { + if (edge->mV0 == edge->mV1 || edge->mV0 < 0 || edge->mV0 >= PointCount || + edge->mV1 < 0 || edge->mV1 >= PointCount) + continue; + + const ESM::Pathgrid::Point& from = pathgrid.mPoints[edge->mV0]; + const ESM::Pathgrid::Point& to = pathgrid.mPoints[edge->mV1]; + + osg::Vec3f fromPos = osg::Vec3f(from.mX, from.mY, from.mZ); + osg::Vec3f toPos = osg::Vec3f(to.mX, to.mY, to.mZ); + osg::Vec3f dir = toPos - fromPos; + dir.normalize(); + + osg::Quat rot = osg::Quat(-osg::PI / 2, osg::Vec3(0, 0, 1)); + dir = rot * dir; + + unsigned short diamondIndex = 0; + if (dir.isNaN()) + diamondIndex = 0; + else if (dir.y() >= 0 && dir.x() > 0) + diamondIndex = 3; + else if (dir.x() <= 0 && dir.y() > 0) + diamondIndex = 1; + else if (dir.y() <= 0 && dir.x() < 0) + diamondIndex = 0; + else if (dir.x() >= 0 && dir.y() < 0) + diamondIndex = 2; + + unsigned short fromIndex = static_cast(edge->mV0); + unsigned short toIndex = static_cast(edge->mV1); + + lineIndices->setElement(lineIndex++, fromIndex * DiamondTotalVertexCount + DiamondVertexCount + diamondIndex); + lineIndices->setElement(lineIndex++, toIndex * DiamondTotalVertexCount + DiamondVertexCount + diamondIndex); + } + + lineIndices->resize(lineIndex); + + gridGeometry->setVertexArray(vertices); + gridGeometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX); + gridGeometry->addPrimitiveSet(pointIndices); + gridGeometry->addPrimitiveSet(lineIndices); + gridGeometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + + return gridGeometry; + } + + osg::ref_ptr createPathgridSelectedWireframe(const ESM::Pathgrid& pathgrid, + const std::vector& selected) + { + const unsigned short PointCount = selected.size(); + + const unsigned short VertexCount = PointCount * DiamondVertexCount; + const unsigned short ColorCount = VertexCount; + const size_t IndexCount = PointCount * DiamondWireframeIndexCount; + + osg::ref_ptr wireframeGeometry = new osg::Geometry(); + + osg::ref_ptr vertices = new osg::Vec3Array(VertexCount); + osg::ref_ptr colors = new osg::Vec4Array(ColorCount); + osg::ref_ptr indices = + new osg::DrawElementsUShort(osg::PrimitiveSet::LINES, IndexCount); + + osg::Vec3f wireOffset = osg::Vec3f(0, 0, (1 - DiamondWireframeScalar) * DiamondHalfHeight); + + // Add each point/node + for (unsigned short it = 0; it < PointCount; ++it) + { + const ESM::Pathgrid::Point& point = pathgrid.mPoints[selected[it]]; + osg::Vec3f position = osg::Vec3f(point.mX, point.mY, point.mZ) + wireOffset; + + unsigned short vertexOffset = it * DiamondVertexCount; + unsigned short indexOffset = it * DiamondWireframeIndexCount; + + // Point + for (unsigned short i = 0; i < DiamondVertexCount; ++i) + { + (*vertices)[vertexOffset + i] = position + DiamondPoints[i] * DiamondWireframeScalar; + + if (it == PointCount - 1) + (*colors)[vertexOffset + i] = DiamondFocusWireColor; + else + (*colors)[vertexOffset + i] = DiamondWireColor; + } + + for (unsigned short i = 0; i < DiamondWireframeIndexCount; ++i) + { + indices->setElement(indexOffset + i, vertexOffset + DiamondWireframeIndices[i]); + } + } + + wireframeGeometry->setVertexArray(vertices); + wireframeGeometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX); + wireframeGeometry->addPrimitiveSet(indices); + wireframeGeometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + + return wireframeGeometry; + } + + unsigned short getPathgridNode(unsigned short vertexIndex) + { + return vertexIndex / (DiamondVertexCount + DiamondConnectorVertexCount); + } +} diff --git a/components/sceneutil/pathgridutil.hpp b/components/sceneutil/pathgridutil.hpp new file mode 100644 index 0000000000..de0ff35ed3 --- /dev/null +++ b/components/sceneutil/pathgridutil.hpp @@ -0,0 +1,25 @@ +#ifndef OPENMW_COMPONENTS_PATHGRIDUTIL_H +#define OPENMW_COMPONENTS_PATHGRIDUTIL_H + +#include +#include + +namespace ESM +{ + struct Pathgrid; +} + +namespace SceneUtil +{ + const float DiamondHalfHeight = 40.f; + const float DiamondHalfWidth = 16.f; + + osg::ref_ptr createPathgridGeometry(const ESM::Pathgrid& pathgrid); + + osg::ref_ptr createPathgridSelectedWireframe(const ESM::Pathgrid& pathgrid, + const std::vector& selected); + + unsigned short getPathgridNode(unsigned short vertexIndex); +} + +#endif diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index 88b907fafa..3af5176f4f 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -4,9 +4,6 @@ #include #include -#include -#include - #include "skeleton.hpp" #include "util.hpp" @@ -74,6 +71,7 @@ RigGeometry::RigGeometry() setCullCallback(new UpdateRigGeometry); setUpdateCallback(new UpdateRigBounds); setSupportsDisplayList(false); + setUseVertexBufferObjects(true); setComputeBoundingBoxCallback(new DummyComputeBoundCallback); } @@ -96,34 +94,53 @@ void RigGeometry::setSourceGeometry(osg::ref_ptr sourceGeometry) if (from.getStateSet()) setStateSet(from.getStateSet()); - // copy over primitive sets. - getPrimitiveSetList() = from.getPrimitiveSetList(); + // shallow copy primitive sets & vertex attributes that we will not modify + setPrimitiveSetList(from.getPrimitiveSetList()); + setColorArray(from.getColorArray()); + setSecondaryColorArray(from.getSecondaryColorArray()); + setFogCoordArray(from.getFogCoordArray()); - if (from.getColorArray()) - setColorArray(from.getColorArray()); + // need to copy over texcoord list manually due to a missing null pointer check in setTexCoordArrayList(), this has been fixed in OSG 3.5 + osg::Geometry::ArrayList& texCoordList = from.getTexCoordArrayList(); + for (unsigned int i=0; i vbo (new osg::VertexBufferObject); + vbo->setUsage(GL_DYNAMIC_DRAW_ARB); - for(unsigned int ti=0;ti vertexArray = osg::clone(from.getVertexArray(), osg::CopyOp::DEEP_COPY_ALL); + if (vertexArray) { - if (from.getTexCoordArray(ti)) - setTexCoordArray(ti,from.getTexCoordArray(ti)); + vertexArray->setVertexBufferObject(vbo); + setVertexArray(vertexArray); } - osg::Geometry::ArrayList& arrayList = from.getVertexAttribArrayList(); - for(unsigned int vi=0;vi< arrayList.size();++vi) + osg::ref_ptr normalArray = osg::clone(from.getNormalArray(), osg::CopyOp::DEEP_COPY_ALL); + if (normalArray) { - osg::Array* array = arrayList[vi].get(); - if (array) - setVertexAttribArray(vi,array); + normalArray->setVertexBufferObject(vbo); + setNormalArray(normalArray, osg::Array::BIND_PER_VERTEX); } - setVertexArray(dynamic_cast(from.getVertexArray()->clone(osg::CopyOp::DEEP_COPY_ALL))); - setNormalArray(dynamic_cast(from.getNormalArray()->clone(osg::CopyOp::DEEP_COPY_ALL)), osg::Array::BIND_PER_VERTEX); + if (osg::Vec4Array* tangents = dynamic_cast(from.getTexCoordArray(7))) + { + mSourceTangents = tangents; + osg::ref_ptr tangentArray = osg::clone(tangents, osg::CopyOp::DEEP_COPY_ALL); + tangentArray->setVertexBufferObject(vbo); + setTexCoordArray(7, tangentArray, osg::Array::BIND_PER_VERTEX); + } + else + mSourceTangents = NULL; +} + +osg::ref_ptr RigGeometry::getSourceGeometry() +{ + return mSourceGeometry; } bool RigGeometry::initFromParentSkeleton(osg::NodeVisitor* nv) @@ -177,7 +194,7 @@ bool RigGeometry::initFromParentSkeleton(osg::NodeVisitor* nv) } } - for (Vertex2BoneMap::iterator it = vertex2BoneMap.begin(); it != vertex2BoneMap.end(); it++) + for (Vertex2BoneMap::iterator it = vertex2BoneMap.begin(); it != vertex2BoneMap.end(); ++it) { mBone2VertexMap[it->second].push_back(it->first); } @@ -224,14 +241,16 @@ void RigGeometry::update(osg::NodeVisitor* nv) return; mLastFrameNumber = nv->getTraversalNumber(); - mSkeleton->updateBoneMatrices(nv); + mSkeleton->updateBoneMatrices(nv->getTraversalNumber()); // skinning osg::Vec3Array* positionSrc = static_cast(mSourceGeometry->getVertexArray()); osg::Vec3Array* normalSrc = static_cast(mSourceGeometry->getNormalArray()); + osg::Vec4Array* tangentSrc = mSourceTangents; osg::Vec3Array* positionDst = static_cast(getVertexArray()); osg::Vec3Array* normalDst = static_cast(getNormalArray()); + osg::Vec4Array* tangentDst = static_cast(getTexCoordArray(7)); for (Bone2VertexMap::const_iterator it = mBone2VertexMap.begin(); it != mBone2VertexMap.end(); ++it) { @@ -255,11 +274,19 @@ void RigGeometry::update(osg::NodeVisitor* nv) unsigned short vertex = *vertexIt; (*positionDst)[vertex] = resultMat.preMult((*positionSrc)[vertex]); (*normalDst)[vertex] = osg::Matrix::transform3x3((*normalSrc)[vertex], resultMat); + if (tangentDst) + { + osg::Vec4f srcTangent = (*tangentSrc)[vertex]; + osg::Vec3f transformedTangent = osg::Matrix::transform3x3(osg::Vec3f(srcTangent.x(), srcTangent.y(), srcTangent.z()), resultMat); + (*tangentDst)[vertex] = osg::Vec4f(transformedTangent, srcTangent.w()); + } } } positionDst->dirty(); normalDst->dirty(); + if (tangentDst) + tangentDst->dirty(); } void RigGeometry::updateBounds(osg::NodeVisitor *nv) @@ -274,9 +301,9 @@ void RigGeometry::updateBounds(osg::NodeVisitor *nv) return; mBoundsFirstFrame = false; - mSkeleton->updateBoneMatrices(nv); + mSkeleton->updateBoneMatrices(nv->getTraversalNumber()); - updateGeomToSkelMatrix(nv); + updateGeomToSkelMatrix(nv->getNodePath()); osg::BoundingBox box; for (BoneSphereMap::const_iterator it = mBoneSphereMap.begin(); it != mBoneSphereMap.end(); ++it) @@ -288,21 +315,17 @@ void RigGeometry::updateBounds(osg::NodeVisitor *nv) } _boundingBox = box; - _boundingBoxComputed = true; -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) - // in OSG 3.3.3 and up Drawable inherits from Node, so has a bounding sphere as well. _boundingSphere = osg::BoundingSphere(_boundingBox); _boundingSphereComputed = true; -#endif for (unsigned int i=0; idirtyBound(); } -void RigGeometry::updateGeomToSkelMatrix(osg::NodeVisitor *nv) +void RigGeometry::updateGeomToSkelMatrix(const osg::NodePath& nodePath) { mSkelToGeomPath.clear(); bool foundSkel = false; - for (osg::NodePath::const_iterator it = nv->getNodePath().begin(); it != nv->getNodePath().end(); ++it) + for (osg::NodePath::const_iterator it = nodePath.begin(); it != nodePath.end(); ++it) { if (!foundSkel) { diff --git a/components/sceneutil/riggeometry.hpp b/components/sceneutil/riggeometry.hpp index 03c287b812..250c86b0e4 100644 --- a/components/sceneutil/riggeometry.hpp +++ b/components/sceneutil/riggeometry.hpp @@ -13,6 +13,9 @@ namespace SceneUtil /// @brief Mesh skinning implementation. /// @note A RigGeometry may be attached directly to a Skeleton, or somewhere below a Skeleton. /// Note though that the RigGeometry ignores any transforms below the Skeleton, so the attachment point is not that important. + /// @note To avoid race conditions, the rig geometry needs to be double buffered. This can be done + /// using a FrameSwitch node that has two RigGeometry children. In the future we may want to consider implementing + /// the double buffering inside RigGeometry. class RigGeometry : public osg::Geometry { public: @@ -36,8 +39,12 @@ namespace SceneUtil void setInfluenceMap(osg::ref_ptr influenceMap); + /// Initialize this geometry from the source geometry. + /// @note The source geometry will not be modified. void setSourceGeometry(osg::ref_ptr sourceGeom); + osg::ref_ptr getSourceGeometry(); + // Called automatically by our CullCallback void update(osg::NodeVisitor* nv); @@ -46,6 +53,7 @@ namespace SceneUtil private: osg::ref_ptr mSourceGeometry; + osg::ref_ptr mSourceTangents; Skeleton* mSkeleton; osg::NodePath mSkelToGeomPath; @@ -72,7 +80,7 @@ namespace SceneUtil bool initFromParentSkeleton(osg::NodeVisitor* nv); - void updateGeomToSkelMatrix(osg::NodeVisitor* nv); + void updateGeomToSkelMatrix(const osg::NodePath& nodePath); }; } diff --git a/components/sceneutil/skeleton.cpp b/components/sceneutil/skeleton.cpp index d1299c0587..390a8e8235 100644 --- a/components/sceneutil/skeleton.cpp +++ b/components/sceneutil/skeleton.cpp @@ -38,6 +38,8 @@ Skeleton::Skeleton() , mNeedToUpdateBoneMatrices(true) , mActive(true) , mLastFrameNumber(0) + , mTraversedEvenFrame(false) + , mTraversedOddFrame(false) { } @@ -48,6 +50,8 @@ Skeleton::Skeleton(const Skeleton ©, const osg::CopyOp ©op) , mNeedToUpdateBoneMatrices(true) , mActive(copy.mActive) , mLastFrameNumber(0) + , mTraversedEvenFrame(false) + , mTraversedOddFrame(false) { } @@ -104,12 +108,17 @@ Bone* Skeleton::getBone(const std::string &name) return bone; } -void Skeleton::updateBoneMatrices(osg::NodeVisitor* nv) +void Skeleton::updateBoneMatrices(unsigned int traversalNumber) { - if (nv->getTraversalNumber() != mLastFrameNumber) + if (traversalNumber != mLastFrameNumber) mNeedToUpdateBoneMatrices = true; - mLastFrameNumber = nv->getTraversalNumber(); + mLastFrameNumber = traversalNumber; + + if (mLastFrameNumber % 2 == 0) + mTraversedEvenFrame = true; + else + mTraversedOddFrame = true; if (mNeedToUpdateBoneMatrices) { @@ -135,12 +144,18 @@ bool Skeleton::getActive() const return mActive; } +void Skeleton::markDirty() +{ + mTraversedEvenFrame = false; + mTraversedOddFrame = false; +} + void Skeleton::traverse(osg::NodeVisitor& nv) { if (!getActive() && nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR // need to process at least 2 frames before shutting off update, since we need to have both frame-alternating RigGeometries initialized // this would be more naturally handled if the double-buffering was implemented in RigGeometry itself rather than in a FrameSwitch decorator node - && mLastFrameNumber != 0 && mLastFrameNumber+2 <= nv.getTraversalNumber()) + && mLastFrameNumber != 0 && mTraversedEvenFrame && mTraversedOddFrame) return; osg::Group::traverse(nv); } diff --git a/components/sceneutil/skeleton.hpp b/components/sceneutil/skeleton.hpp index d98d367518..764b7f6459 100644 --- a/components/sceneutil/skeleton.hpp +++ b/components/sceneutil/skeleton.hpp @@ -45,7 +45,7 @@ namespace SceneUtil Bone* getBone(const std::string& name); /// Request an update of bone matrices. May be a no-op if already updated in this frame. - void updateBoneMatrices(osg::NodeVisitor* nv); + void updateBoneMatrices(unsigned int traversalNumber); /// Set the skinning active flag. Inactive skeletons will not have their child rigs updated. /// You should set this flag to false if you know that bones are not currently moving. @@ -53,6 +53,9 @@ namespace SceneUtil bool getActive() const; + /// If a new RigGeometry is added after the Skeleton has already been rendered, you must call markDirty(). + void markDirty(); + void traverse(osg::NodeVisitor& nv); private: @@ -69,6 +72,8 @@ namespace SceneUtil bool mActive; unsigned int mLastFrameNumber; + bool mTraversedEvenFrame; + bool mTraversedOddFrame; }; } diff --git a/components/sceneutil/unrefqueue.cpp b/components/sceneutil/unrefqueue.cpp new file mode 100644 index 0000000000..10d5682981 --- /dev/null +++ b/components/sceneutil/unrefqueue.cpp @@ -0,0 +1,48 @@ +#include "unrefqueue.hpp" + +#include + +#include +//#include +//#include + +#include + +namespace SceneUtil +{ + + class UnrefWorkItem : public SceneUtil::WorkItem + { + public: + std::deque > mObjects; + + virtual void doWork() + { + //osg::Timer timer; + //size_t objcount = mObjects.size(); + mObjects.clear(); + //std::cout << "cleared " << objcount << " objects in " << timer.time_m() << std::endl; + } + }; + + UnrefQueue::UnrefQueue() + { + mWorkItem = new UnrefWorkItem; + } + + void UnrefQueue::push(const osg::Object *obj) + { + mWorkItem->mObjects.push_back(obj); + } + + void UnrefQueue::flush(SceneUtil::WorkQueue *workQueue) + { + if (mWorkItem->mObjects.empty()) + return; + + workQueue->addWorkItem(mWorkItem, true); + + mWorkItem = new UnrefWorkItem; + } + +} diff --git a/components/sceneutil/unrefqueue.hpp b/components/sceneutil/unrefqueue.hpp new file mode 100644 index 0000000000..85b1d31e1d --- /dev/null +++ b/components/sceneutil/unrefqueue.hpp @@ -0,0 +1,37 @@ +#ifndef OPENMW_COMPONENTS_UNREFQUEUE_H +#define OPENMW_COMPONENTS_UNREFQUEUE_H + +#include +#include + +namespace osg +{ + class Object; +} + +namespace SceneUtil +{ + class WorkQueue; + class UnrefWorkItem; + + /// @brief Handles unreferencing of objects through the WorkQueue. Typical use scenario + /// would be the main thread pushing objects that are no longer needed, and the background thread deleting them. + class UnrefQueue : public osg::Referenced + { + public: + UnrefQueue(); + + /// Adds an object to the list of objects to be unreferenced. Call from the main thread. + void push(const osg::Object* obj); + + /// Adds a WorkItem to the given WorkQueue that will clear the list of objects in a worker thread, thus unreferencing them. + /// Call from the main thread. + void flush(SceneUtil::WorkQueue* workQueue); + + private: + osg::ref_ptr mWorkItem; + }; + +} + +#endif diff --git a/components/sceneutil/visitor.cpp b/components/sceneutil/visitor.cpp index 0a5ad2d006..d147ced7f3 100644 --- a/components/sceneutil/visitor.cpp +++ b/components/sceneutil/visitor.cpp @@ -1,7 +1,5 @@ #include "visitor.hpp" -#include - #include #include @@ -19,12 +17,6 @@ namespace SceneUtil traverse(group); } - void DisableFreezeOnCullVisitor::apply(osg::Geode &geode) - { - for (unsigned int i=0; i(&drw)) diff --git a/components/sceneutil/visitor.hpp b/components/sceneutil/visitor.hpp index dcfefe9cd6..751339d02d 100644 --- a/components/sceneutil/visitor.hpp +++ b/components/sceneutil/visitor.hpp @@ -34,7 +34,6 @@ namespace SceneUtil { } - virtual void apply(osg::Geode &geode); virtual void apply(osg::Drawable& drw); }; diff --git a/components/sceneutil/workqueue.cpp b/components/sceneutil/workqueue.cpp index b642687f0f..0685493d08 100644 --- a/components/sceneutil/workqueue.cpp +++ b/components/sceneutil/workqueue.cpp @@ -1,9 +1,11 @@ #include "workqueue.hpp" +#include + namespace SceneUtil { -void WorkTicket::waitTillDone() +void WorkItem::waitTillDone() { if (mDone > 0) return; @@ -15,7 +17,7 @@ void WorkTicket::waitTillDone() } } -void WorkTicket::signalDone() +void WorkItem::signalDone() { { OpenThreads::ScopedLock lock(mMutex); @@ -25,23 +27,16 @@ void WorkTicket::signalDone() } WorkItem::WorkItem() - : mTicket(new WorkTicket) { - mTicket->setThreadSafeRefUnref(true); } WorkItem::~WorkItem() { } -void WorkItem::doWork() +bool WorkItem::isDone() const { - mTicket->signalDone(); -} - -osg::ref_ptr WorkItem::getTicket() -{ - return mTicket; + return (mDone > 0); } WorkQueue::WorkQueue(int workerThreads) @@ -59,12 +54,8 @@ WorkQueue::~WorkQueue() { { OpenThreads::ScopedLock lock(mMutex); - while (mQueue.size()) - { - WorkItem* item = mQueue.front(); - delete item; - mQueue.pop(); - } + while (!mQueue.empty()) + mQueue.pop_back(); mIsReleased = true; mCondition.broadcast(); } @@ -76,26 +67,33 @@ WorkQueue::~WorkQueue() } } -osg::ref_ptr WorkQueue::addWorkItem(WorkItem *item) +void WorkQueue::addWorkItem(osg::ref_ptr item, bool front) { - osg::ref_ptr ticket = item->getTicket(); + if (item->isDone()) + { + std::cerr << "warning, trying to add a work item that is already completed" << std::endl; + return; + } + OpenThreads::ScopedLock lock(mMutex); - mQueue.push(item); + if (front) + mQueue.push_front(item); + else + mQueue.push_back(item); mCondition.signal(); - return ticket; } -WorkItem *WorkQueue::removeWorkItem() +osg::ref_ptr WorkQueue::removeWorkItem() { OpenThreads::ScopedLock lock(mMutex); - while (!mQueue.size() && !mIsReleased) + while (mQueue.empty() && !mIsReleased) { mCondition.wait(&mMutex); } - if (mQueue.size()) + if (!mQueue.empty()) { - WorkItem* item = mQueue.front(); - mQueue.pop(); + osg::ref_ptr item = mQueue.front(); + mQueue.pop_front(); return item; } else @@ -111,11 +109,11 @@ void WorkThread::run() { while (true) { - WorkItem* item = mWorkQueue->removeWorkItem(); + osg::ref_ptr item = mWorkQueue->removeWorkItem(); if (!item) return; item->doWork(); - delete item; + item->signalDone(); } } diff --git a/components/sceneutil/workqueue.hpp b/components/sceneutil/workqueue.hpp index 492bbd090e..e7b0151dd6 100644 --- a/components/sceneutil/workqueue.hpp +++ b/components/sceneutil/workqueue.hpp @@ -14,37 +14,61 @@ namespace SceneUtil { - class WorkTicket : public osg::Referenced - { - public: - void waitTillDone(); - - void signalDone(); - - private: - OpenThreads::Atomic mDone; - OpenThreads::Mutex mMutex; - OpenThreads::Condition mCondition; - }; - - class WorkItem + class WorkItem : public osg::Referenced { public: WorkItem(); virtual ~WorkItem(); /// Override in a derived WorkItem to perform actual work. - /// By default, just signals the ticket that the work is done. - virtual void doWork(); + virtual void doWork() {} - osg::ref_ptr getTicket(); + bool isDone() const; + + /// Wait until the work is completed. Usually called from the main thread. + void waitTillDone(); + + /// Internal use by the WorkQueue. + void signalDone(); protected: - osg::ref_ptr mTicket; + OpenThreads::Atomic mDone; + OpenThreads::Mutex mMutex; + OpenThreads::Condition mCondition; }; - class WorkQueue; + class WorkThread; + /// @brief A work queue that users can push work items onto, to be completed by one or more background threads. + /// @note Work items will be processed in the order that they were given in, however + /// if multiple work threads are involved then it is possible for a later item to complete before earlier items. + class WorkQueue : public osg::Referenced + { + public: + WorkQueue(int numWorkerThreads=1); + ~WorkQueue(); + + /// Add a new work item to the back of the queue. + /// @par The work item's waitTillDone() method may be used by the caller to wait until the work is complete. + /// @param front If true, add item to the front of the queue. If false (default), add to the back. + void addWorkItem(osg::ref_ptr item, bool front=false); + + /// Get the next work item from the front of the queue. If the queue is empty, waits until a new item is added. + /// If the workqueue is in the process of being destroyed, may return NULL. + /// @par Used internally by the WorkThread. + osg::ref_ptr removeWorkItem(); + + private: + bool mIsReleased; + std::deque > mQueue; + + OpenThreads::Mutex mMutex; + OpenThreads::Condition mCondition; + + std::vector mThreads; + }; + + /// Internally used by WorkQueue. class WorkThread : public OpenThreads::Thread { public: @@ -56,35 +80,6 @@ namespace SceneUtil WorkQueue* mWorkQueue; }; - /// @brief A work queue that users can push work items onto, to be completed by one or more background threads. - class WorkQueue - { - public: - WorkQueue(int numWorkerThreads=1); - ~WorkQueue(); - - /// Add a new work item to the back of the queue. - /// @par The returned WorkTicket may be used by the caller to wait until the work is complete. - osg::ref_ptr addWorkItem(WorkItem* item); - - /// Get the next work item from the front of the queue. If the queue is empty, waits until a new item is added. - /// If the workqueue is in the process of being destroyed, may return NULL. - /// @note The caller must free the returned WorkItem - WorkItem* removeWorkItem(); - - void runThread(); - - private: - bool mIsReleased; - std::queue mQueue; - - OpenThreads::Mutex mMutex; - OpenThreads::Condition mCondition; - - std::vector mThreads; - }; - - } diff --git a/components/sdlutil/sdlcursormanager.cpp b/components/sdlutil/sdlcursormanager.cpp index 9ecef04839..ad03083deb 100644 --- a/components/sdlutil/sdlcursormanager.cpp +++ b/components/sdlutil/sdlcursormanager.cpp @@ -211,12 +211,12 @@ namespace SDLUtil SDL_SetCursor(mCursorMap.find(name)->second); } - void SDLCursorManager::createCursor(const std::string& name, int rotDegrees, osg::Image* image, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y) + void SDLCursorManager::createCursor(const std::string& name, int rotDegrees, osg::Image* image, Uint8 hotspot_x, Uint8 hotspot_y) { - _createCursorFromResource(name, rotDegrees, image, size_x, size_y, hotspot_x, hotspot_y); + _createCursorFromResource(name, rotDegrees, image, hotspot_x, hotspot_y); } - void SDLCursorManager::_createCursorFromResource(const std::string& name, int rotDegrees, osg::Image* image, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y) + void SDLCursorManager::_createCursorFromResource(const std::string& name, int rotDegrees, osg::Image* image, Uint8 hotspot_x, Uint8 hotspot_y) { osg::ref_ptr decompressed; @@ -231,7 +231,7 @@ namespace SDLUtil return; } - SDL_Surface* surf = SDLUtil::imageToSurface(decompressed, false); + SDL_Surface* surf = SDLUtil::imageToSurface(decompressed, true); //set the cursor and store it for later SDL_Cursor* curs = SDL_CreateColorCursor(surf, hotspot_x, hotspot_y); @@ -239,8 +239,6 @@ namespace SDLUtil //clean up SDL_FreeSurface(surf); - - _setGUICursor(name); } } diff --git a/components/sdlutil/sdlcursormanager.hpp b/components/sdlutil/sdlcursormanager.hpp index 0db578039c..f338778d1a 100644 --- a/components/sdlutil/sdlcursormanager.hpp +++ b/components/sdlutil/sdlcursormanager.hpp @@ -29,10 +29,10 @@ namespace SDLUtil /// name of the cursor we changed to ("arrow", "ibeam", etc) virtual void cursorChanged(const std::string &name); - virtual void createCursor(const std::string &name, int rotDegrees, osg::Image* image, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y); + virtual void createCursor(const std::string &name, int rotDegrees, osg::Image* image, Uint8 hotspot_x, Uint8 hotspot_y); private: - void _createCursorFromResource(const std::string &name, int rotDegrees, osg::Image* image, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y); + void _createCursorFromResource(const std::string &name, int rotDegrees, osg::Image* image, Uint8 hotspot_x, Uint8 hotspot_y); void _putPixel(SDL_Surface *surface, int x, int y, Uint32 pixel); void _setGUICursor(const std::string& name); diff --git a/components/sdlutil/sdlgraphicswindow.cpp b/components/sdlutil/sdlgraphicswindow.cpp index a0483e84df..c2cf2536e3 100644 --- a/components/sdlutil/sdlgraphicswindow.cpp +++ b/components/sdlutil/sdlgraphicswindow.cpp @@ -2,8 +2,6 @@ #include -#include - namespace SDLUtil { @@ -93,7 +91,7 @@ void GraphicsWindowSDL2::init() SDL_Window *oldWin = SDL_GL_GetCurrentWindow(); SDL_GLContext oldCtx = SDL_GL_GetCurrentContext(); -#ifdef OPENGL_ES +#if defined(OPENGL_ES) || defined(ANDROID) SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 1); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); @@ -112,11 +110,7 @@ void GraphicsWindowSDL2::init() mValid = true; -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,4) getEventQueue()->syncWindowRectangleWithGraphicsContext(); -#else - getEventQueue()->syncWindowRectangleWithGraphcisContext(); -#endif } @@ -133,11 +127,7 @@ bool GraphicsWindowSDL2::realizeImplementation() SDL_ShowWindow(mWindow); -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,4) getEventQueue()->syncWindowRectangleWithGraphicsContext(); -#else - getEventQueue()->syncWindowRectangleWithGraphcisContext(); -#endif mRealized = true; diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp new file mode 100644 index 0000000000..ce77f46dc2 --- /dev/null +++ b/components/shader/shadermanager.cpp @@ -0,0 +1,163 @@ +#include "shadermanager.hpp" + +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +namespace Shader +{ + + void ShaderManager::setShaderPath(const std::string &path) + { + mPath = path; + } + + bool parseIncludes(boost::filesystem::path shaderPath, std::string& source) + { + boost::replace_all(source, "\r\n", "\n"); + + std::set includedFiles; + size_t foundPos = 0; + int fileNumber = 1; + while ((foundPos = source.find("#include")) != std::string::npos) + { + size_t start = source.find('"', foundPos); + if (start == std::string::npos || start == source.size()-1) + { + std::cerr << "Invalid #include " << std::endl; + return false; + } + size_t end = source.find('"', start+1); + if (end == std::string::npos) + { + std::cerr << "Invalid #include " << std::endl; + return false; + } + std::string includeFilename = source.substr(start+1, end-(start+1)); + boost::filesystem::path includePath = shaderPath / includeFilename; + boost::filesystem::ifstream includeFstream; + includeFstream.open(includePath); + if (includeFstream.fail()) + { + std::cerr << "Failed to open " << includePath.string() << std::endl; + return false; + } + + std::stringstream buffer; + buffer << includeFstream.rdbuf(); + + // insert #line directives so we get correct line numbers in compiler errors + int includedFileNumber = fileNumber++; + + int lineNumber = std::count(source.begin(), source.begin() + foundPos, '\n'); + + std::stringstream toInsert; + toInsert << "#line 0 " << includedFileNumber << "\n" << buffer.str() << "\n#line " << lineNumber << " 0\n"; + + source.replace(foundPos, (end-foundPos+1), toInsert.str()); + + if (includedFiles.insert(includePath).second == false) + { + std::cerr << "Detected cyclic #includes" << std::endl; + return false; + } + } + return true; + } + + bool parseDefines(std::string& source, const ShaderManager::DefineMap& defines) + { + const char escapeCharacter = '@'; + size_t foundPos = 0; + while ((foundPos = source.find(escapeCharacter)) != std::string::npos) + { + size_t endPos = source.find_first_of(" \n\r()[].;", foundPos); + if (endPos == std::string::npos) + { + std::cerr << "Unexpected EOF" << std::endl; + return false; + } + std::string define = source.substr(foundPos+1, endPos - (foundPos+1)); + ShaderManager::DefineMap::const_iterator defineFound = defines.find(define); + if (defineFound == defines.end()) + { + std::cerr << "Undefined " << define << std::endl; + return false; + } + else + { + source.replace(foundPos, endPos-foundPos, defineFound->second); + } + } + return true; + } + + osg::ref_ptr ShaderManager::getShader(const std::string &shaderTemplate, const ShaderManager::DefineMap &defines, osg::Shader::Type shaderType) + { + OpenThreads::ScopedLock lock(mMutex); + + // read the template if we haven't already + TemplateMap::iterator templateIt = mShaderTemplates.find(shaderTemplate); + if (templateIt == mShaderTemplates.end()) + { + boost::filesystem::path p = (boost::filesystem::path(mPath) / shaderTemplate); + boost::filesystem::ifstream stream; + stream.open(p); + if (stream.fail()) + { + std::cerr << "Failed to open " << p.string() << std::endl; + return NULL; + } + std::stringstream buffer; + buffer << stream.rdbuf(); + + // parse includes + std::string source = buffer.str(); + if (!parseIncludes(boost::filesystem::path(mPath), source)) + return NULL; + + templateIt = mShaderTemplates.insert(std::make_pair(shaderTemplate, source)).first; + } + + ShaderMap::iterator shaderIt = mShaders.find(std::make_pair(shaderTemplate, defines)); + if (shaderIt == mShaders.end()) + { + std::string shaderSource = templateIt->second; + if (!parseDefines(shaderSource, defines)) + return NULL; + + osg::ref_ptr shader (new osg::Shader(shaderType)); + shader->setShaderSource(shaderSource); + // Assign a unique name to allow the SharedStateManager to compare shaders efficiently + static unsigned int counter = 0; + shader->setName(boost::lexical_cast(counter++)); + + shaderIt = mShaders.insert(std::make_pair(std::make_pair(shaderTemplate, defines), shader)).first; + } + return shaderIt->second; + } + + osg::ref_ptr ShaderManager::getProgram(osg::ref_ptr vertexShader, osg::ref_ptr fragmentShader) + { + OpenThreads::ScopedLock lock(mMutex); + ProgramMap::iterator found = mPrograms.find(std::make_pair(vertexShader, fragmentShader)); + if (found == mPrograms.end()) + { + osg::ref_ptr program (new osg::Program); + program->addShader(vertexShader); + program->addShader(fragmentShader); + found = mPrograms.insert(std::make_pair(std::make_pair(vertexShader, fragmentShader), program)).first; + } + return found->second; + } + +} diff --git a/components/shader/shadermanager.hpp b/components/shader/shadermanager.hpp new file mode 100644 index 0000000000..5196dbe809 --- /dev/null +++ b/components/shader/shadermanager.hpp @@ -0,0 +1,55 @@ +#ifndef OPENMW_COMPONENTS_SHADERMANAGER_H +#define OPENMW_COMPONENTS_SHADERMANAGER_H + +#include +#include + +#include + +#include + +#include + +namespace Shader +{ + + /// @brief Reads shader template files and turns them into a concrete shader, based on a list of define's. + /// @par Shader templates can get the value of a define with the syntax @define. + class ShaderManager + { + public: + void setShaderPath(const std::string& path); + + typedef std::map DefineMap; + + /// Create or retrieve a shader instance. + /// @param shaderTemplate The filename of the shader template. + /// @param defines Define values that can be retrieved by the shader template. + /// @param shaderType The type of shader (usually vertex or fragment shader). + /// @note May return NULL on failure. + /// @note Thread safe. + osg::ref_ptr getShader(const std::string& shaderTemplate, const DefineMap& defines, osg::Shader::Type shaderType); + + osg::ref_ptr getProgram(osg::ref_ptr vertexShader, osg::ref_ptr fragmentShader); + + + private: + std::string mPath; + + // + typedef std::map TemplateMap; + TemplateMap mShaderTemplates; + + typedef std::pair MapKey; + typedef std::map > ShaderMap; + ShaderMap mShaders; + + typedef std::map, osg::ref_ptr >, osg::ref_ptr > ProgramMap; + ProgramMap mPrograms; + + OpenThreads::Mutex mMutex; + }; + +} + +#endif diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp new file mode 100644 index 0000000000..dbba721c22 --- /dev/null +++ b/components/shader/shadervisitor.cpp @@ -0,0 +1,405 @@ +#include "shadervisitor.hpp" + +#include + +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include + +#include "shadermanager.hpp" + +namespace Shader +{ + + ShaderVisitor::ShaderRequirements::ShaderRequirements() + : mShaderRequired(false) + , mColorMaterial(false) + , mVertexColorMode(GL_AMBIENT_AND_DIFFUSE) + , mMaterialOverridden(false) + , mNormalHeight(false) + , mTexStageRequiringTangents(-1) + { + } + + ShaderVisitor::ShaderRequirements::~ShaderRequirements() + { + + } + + ShaderVisitor::ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string &defaultVsTemplate, const std::string &defaultFsTemplate) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mForceShaders(false) + , mClampLighting(true) + , mForcePerPixelLighting(false) + , mAllowedToModifyStateSets(true) + , mAutoUseNormalMaps(false) + , mAutoUseSpecularMaps(false) + , mShaderManager(shaderManager) + , mImageManager(imageManager) + , mDefaultVsTemplate(defaultVsTemplate) + , mDefaultFsTemplate(defaultFsTemplate) + { + mRequirements.push_back(ShaderRequirements()); + } + + void ShaderVisitor::setForceShaders(bool force) + { + mForceShaders = force; + } + + void ShaderVisitor::setClampLighting(bool clamp) + { + mClampLighting = clamp; + } + + void ShaderVisitor::setForcePerPixelLighting(bool force) + { + mForcePerPixelLighting = force; + } + + void ShaderVisitor::apply(osg::Node& node) + { + if (node.getStateSet()) + { + pushRequirements(); + applyStateSet(node.getStateSet(), node); + traverse(node); + popRequirements(); + } + else + traverse(node); + } + + osg::StateSet* getWritableStateSet(osg::Node& node) + { + if (!node.getStateSet()) + return node.getOrCreateStateSet(); + + osg::ref_ptr newStateSet = osg::clone(node.getStateSet(), osg::CopyOp::SHALLOW_COPY); + node.setStateSet(newStateSet); + return newStateSet.get(); + } + + const char* defaultTextures[] = { "diffuseMap", "normalMap", "emissiveMap", "darkMap", "detailMap", "envMap", "specularMap", "decalMap" }; + bool isTextureNameRecognized(const std::string& name) + { + for (unsigned int i=0; i stateset, osg::Node& node) + { + osg::StateSet* writableStateSet = NULL; + if (mAllowedToModifyStateSets) + writableStateSet = node.getStateSet(); + const osg::StateSet::TextureAttributeList& texAttributes = stateset->getTextureAttributeList(); + if (!texAttributes.empty()) + { + const osg::Texture* diffuseMap = NULL; + const osg::Texture* normalMap = NULL; + const osg::Texture* specularMap = NULL; + for(unsigned int unit=0;unitgetTextureAttribute(unit, osg::StateAttribute::TEXTURE); + if (attr) + { + const osg::Texture* texture = attr->asTexture(); + if (texture) + { + std::string texName = texture->getName(); + if ((texName.empty() || !isTextureNameRecognized(texName)) && unit == 0) + texName = "diffuseMap"; + + if (texName == "normalHeightMap") + { + mRequirements.back().mNormalHeight = true; + texName = "normalMap"; + } + + if (!texName.empty()) + { + mRequirements.back().mTextures[unit] = texName; + if (texName == "normalMap") + { + mRequirements.back().mTexStageRequiringTangents = unit; + mRequirements.back().mShaderRequired = true; + if (!writableStateSet) + writableStateSet = getWritableStateSet(node); + // normal maps are by default off since the FFP can't render them, now that we'll use shaders switch to On + writableStateSet->setTextureMode(unit, GL_TEXTURE_2D, osg::StateAttribute::ON); + normalMap = texture; + } + else if (texName == "diffuseMap") + diffuseMap = texture; + else if (texName == "specularMap") + specularMap = texture; + } + else + std::cerr << "ShaderVisitor encountered unknown texture " << texture << std::endl; + } + } + } + + if (mAutoUseNormalMaps && diffuseMap != NULL && normalMap == NULL) + { + std::string normalMap = diffuseMap->getImage(0)->getFileName(); + + osg::ref_ptr image; + bool normalHeight = false; + std::string normalHeightMap = normalMap; + boost::replace_last(normalHeightMap, ".", mNormalHeightMapPattern + "."); + if (mImageManager.getVFS()->exists(normalHeightMap)) + { + image = mImageManager.getImage(normalHeightMap); + normalHeight = true; + } + else + { + boost::replace_last(normalMap, ".", mNormalMapPattern + "."); + if (mImageManager.getVFS()->exists(normalMap)) + { + image = mImageManager.getImage(normalMap); + } + } + + if (image) + { + osg::ref_ptr normalMapTex (new osg::Texture2D(image)); + normalMapTex->setWrap(osg::Texture::WRAP_S, diffuseMap->getWrap(osg::Texture::WRAP_S)); + normalMapTex->setWrap(osg::Texture::WRAP_T, diffuseMap->getWrap(osg::Texture::WRAP_T)); + normalMapTex->setFilter(osg::Texture::MIN_FILTER, diffuseMap->getFilter(osg::Texture::MIN_FILTER)); + normalMapTex->setFilter(osg::Texture::MAG_FILTER, diffuseMap->getFilter(osg::Texture::MAG_FILTER)); + normalMapTex->setMaxAnisotropy(diffuseMap->getMaxAnisotropy()); + normalMapTex->setName("normalMap"); + + int unit = texAttributes.size(); + if (!writableStateSet) + writableStateSet = getWritableStateSet(node); + writableStateSet->setTextureAttributeAndModes(unit, normalMapTex, osg::StateAttribute::ON); + mRequirements.back().mTextures[unit] = "normalMap"; + mRequirements.back().mTexStageRequiringTangents = unit; + mRequirements.back().mShaderRequired = true; + mRequirements.back().mNormalHeight = normalHeight; + } + } + if (mAutoUseSpecularMaps && diffuseMap != NULL && specularMap == NULL) + { + std::string specularMap = diffuseMap->getImage(0)->getFileName(); + boost::replace_last(specularMap, ".", mSpecularMapPattern + "."); + if (mImageManager.getVFS()->exists(specularMap)) + { + osg::ref_ptr specularMapTex (new osg::Texture2D(mImageManager.getImage(specularMap))); + specularMapTex->setWrap(osg::Texture::WRAP_S, diffuseMap->getWrap(osg::Texture::WRAP_S)); + specularMapTex->setWrap(osg::Texture::WRAP_T, diffuseMap->getWrap(osg::Texture::WRAP_T)); + specularMapTex->setFilter(osg::Texture::MIN_FILTER, diffuseMap->getFilter(osg::Texture::MIN_FILTER)); + specularMapTex->setFilter(osg::Texture::MAG_FILTER, diffuseMap->getFilter(osg::Texture::MAG_FILTER)); + specularMapTex->setMaxAnisotropy(diffuseMap->getMaxAnisotropy()); + specularMapTex->setName("specularMap"); + + int unit = texAttributes.size(); + if (!writableStateSet) + writableStateSet = getWritableStateSet(node); + writableStateSet->setTextureAttributeAndModes(unit, specularMapTex, osg::StateAttribute::ON); + mRequirements.back().mTextures[unit] = "specularMap"; + mRequirements.back().mShaderRequired = true; + } + } + } + + const osg::StateSet::AttributeList& attributes = stateset->getAttributeList(); + for (osg::StateSet::AttributeList::const_iterator it = attributes.begin(); it != attributes.end(); ++it) + { + if (it->first.first == osg::StateAttribute::MATERIAL) + { + if (!mRequirements.back().mMaterialOverridden || it->second.second & osg::StateAttribute::PROTECTED) + { + if (it->second.second & osg::StateAttribute::OVERRIDE) + mRequirements.back().mMaterialOverridden = true; + + const osg::Material* mat = static_cast(it->second.first.get()); + mRequirements.back().mColorMaterial = (mat->getColorMode() != osg::Material::OFF); + mRequirements.back().mVertexColorMode = mat->getColorMode(); + } + } + } + } + + void ShaderVisitor::pushRequirements() + { + mRequirements.push_back(mRequirements.back()); + } + + void ShaderVisitor::popRequirements() + { + mRequirements.pop_back(); + } + + void ShaderVisitor::createProgram(const ShaderRequirements &reqs, osg::Node& node) + { + osg::StateSet* writableStateSet = NULL; + if (mAllowedToModifyStateSets) + writableStateSet = node.getOrCreateStateSet(); + else + writableStateSet = getWritableStateSet(node); + + ShaderManager::DefineMap defineMap; + for (unsigned int i=0; i::const_iterator texIt = reqs.mTextures.begin(); texIt != reqs.mTextures.end(); ++texIt) + { + defineMap[texIt->second] = "1"; + defineMap[texIt->second + std::string("UV")] = boost::lexical_cast(texIt->first); + } + + if (!reqs.mColorMaterial) + defineMap["colorMode"] = "0"; + else + { + switch (reqs.mVertexColorMode) + { + default: + case GL_AMBIENT_AND_DIFFUSE: + defineMap["colorMode"] = "2"; + break; + case GL_EMISSION: + defineMap["colorMode"] = "1"; + break; + } + } + + defineMap["forcePPL"] = mForcePerPixelLighting ? "1" : "0"; + defineMap["clamp"] = mClampLighting ? "1" : "0"; + + defineMap["parallax"] = reqs.mNormalHeight ? "1" : "0"; + + osg::ref_ptr vertexShader (mShaderManager.getShader(mDefaultVsTemplate, defineMap, osg::Shader::VERTEX)); + osg::ref_ptr fragmentShader (mShaderManager.getShader(mDefaultFsTemplate, defineMap, osg::Shader::FRAGMENT)); + + if (vertexShader && fragmentShader) + { + writableStateSet->setAttributeAndModes(mShaderManager.getProgram(vertexShader, fragmentShader), osg::StateAttribute::ON); + + for (std::map::const_iterator texIt = reqs.mTextures.begin(); texIt != reqs.mTextures.end(); ++texIt) + { + writableStateSet->addUniform(new osg::Uniform(texIt->second.c_str(), texIt->first), osg::StateAttribute::ON); + } + } + } + + void ShaderVisitor::apply(osg::Geometry& geometry) + { + bool needPop = (geometry.getStateSet() != NULL); + if (geometry.getStateSet()) + { + pushRequirements(); + applyStateSet(geometry.getStateSet(), geometry); + } + + if (!mRequirements.empty()) + { + const ShaderRequirements& reqs = mRequirements.back(); + + osg::ref_ptr sourceGeometry = &geometry; + SceneUtil::RigGeometry* rig = dynamic_cast(&geometry); + if (rig) + sourceGeometry = rig->getSourceGeometry(); + + if (mAllowedToModifyStateSets) + { + // make sure that all UV sets are there + for (std::map::const_iterator it = reqs.mTextures.begin(); it != reqs.mTextures.end(); ++it) + { + if (sourceGeometry->getTexCoordArray(it->first) == NULL) + sourceGeometry->setTexCoordArray(it->first, sourceGeometry->getTexCoordArray(0)); + } + } + + if (reqs.mTexStageRequiringTangents != -1 && mAllowedToModifyStateSets) + { + osg::ref_ptr generator (new osgUtil::TangentSpaceGenerator); + generator->generate(sourceGeometry, reqs.mTexStageRequiringTangents); + + sourceGeometry->setTexCoordArray(7, generator->getTangentArray(), osg::Array::BIND_PER_VERTEX); + } + + if (rig) + rig->setSourceGeometry(sourceGeometry); + + // TODO: find a better place for the stateset + if (reqs.mShaderRequired || mForceShaders) + createProgram(reqs, geometry); + } + + if (needPop) + popRequirements(); + } + + void ShaderVisitor::apply(osg::Drawable& drawable) + { + // non-Geometry drawable (e.g. particle system) + bool needPop = (drawable.getStateSet() != NULL); + + if (drawable.getStateSet()) + { + pushRequirements(); + applyStateSet(drawable.getStateSet(), drawable); + } + + if (!mRequirements.empty()) + { + const ShaderRequirements& reqs = mRequirements.back(); + // TODO: find a better place for the stateset + if (reqs.mShaderRequired || mForceShaders) + createProgram(reqs, drawable); + } + + if (needPop) + popRequirements(); + } + + void ShaderVisitor::setAllowedToModifyStateSets(bool allowed) + { + mAllowedToModifyStateSets = allowed; + } + + void ShaderVisitor::setAutoUseNormalMaps(bool use) + { + mAutoUseNormalMaps = use; + } + + void ShaderVisitor::setNormalMapPattern(const std::string &pattern) + { + mNormalMapPattern = pattern; + } + + void ShaderVisitor::setNormalHeightMapPattern(const std::string &pattern) + { + mNormalHeightMapPattern = pattern; + } + + void ShaderVisitor::setAutoUseSpecularMaps(bool use) + { + mAutoUseSpecularMaps = use; + } + + void ShaderVisitor::setSpecularMapPattern(const std::string &pattern) + { + mSpecularMapPattern = pattern; + } + +} diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp new file mode 100644 index 0000000000..8f4597ff36 --- /dev/null +++ b/components/shader/shadervisitor.hpp @@ -0,0 +1,103 @@ +#ifndef OPENMW_COMPONENTS_SHADERVISITOR_H +#define OPENMW_COMPONENTS_SHADERVISITOR_H + +#include + +namespace Resource +{ + class ImageManager; +} + +namespace Shader +{ + + class ShaderManager; + + /// @brief Adjusts the given subgraph to render using shaders. + class ShaderVisitor : public osg::NodeVisitor + { + public: + ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string& defaultVsTemplate, const std::string& defaultFsTemplate); + + /// By default, only bump mapped objects will have a shader added to them. + /// Setting force = true will cause all objects to render using shaders, regardless of having a bump map. + void setForceShaders(bool force); + + /// Set whether lighting is clamped for visual compatibility with the fixed function pipeline. + void setClampLighting(bool clamp); + + /// By default, only bump mapped objects use per-pixel lighting. + /// Setting force = true will cause all shaders to use per-pixel lighting, regardless of having a bump map. + void setForcePerPixelLighting(bool force); + + /// Set if we are allowed to modify StateSets encountered in the graph (default true). + /// @par If set to false, then instead of modifying, the StateSet will be cloned and this new StateSet will be assigned to the node. + /// @par This option is useful when the ShaderVisitor is run on a "live" subgraph that may have already been submitted for rendering. + void setAllowedToModifyStateSets(bool allowed); + + /// Automatically use normal maps if a file with suitable name exists (see normal map pattern). + void setAutoUseNormalMaps(bool use); + + void setNormalMapPattern(const std::string& pattern); + void setNormalHeightMapPattern(const std::string& pattern); + + void setAutoUseSpecularMaps(bool use); + + void setSpecularMapPattern(const std::string& pattern); + + virtual void apply(osg::Node& node); + + virtual void apply(osg::Drawable& drawable); + virtual void apply(osg::Geometry& geometry); + + void applyStateSet(osg::ref_ptr stateset, osg::Node& node); + + void pushRequirements(); + void popRequirements(); + + private: + bool mForceShaders; + bool mClampLighting; + bool mForcePerPixelLighting; + bool mAllowedToModifyStateSets; + + bool mAutoUseNormalMaps; + std::string mNormalMapPattern; + std::string mNormalHeightMapPattern; + + bool mAutoUseSpecularMaps; + std::string mSpecularMapPattern; + + ShaderManager& mShaderManager; + Resource::ImageManager& mImageManager; + + struct ShaderRequirements + { + ShaderRequirements(); + ~ShaderRequirements(); + + // + std::map mTextures; + + bool mShaderRequired; + + bool mColorMaterial; + // osg::Material::ColorMode + int mVertexColorMode; + bool mMaterialOverridden; + bool mNormalHeight; // true if normal map has height info in alpha channel + + // -1 == no tangents required + int mTexStageRequiringTangents; + }; + std::vector mRequirements; + + std::string mDefaultVsTemplate; + std::string mDefaultFsTemplate; + + void createProgram(const ShaderRequirements& reqs, osg::Node& node); + }; + +} + +#endif diff --git a/components/terrain/buffercache.cpp b/components/terrain/buffercache.cpp index a64f8ffd1d..a72dac0262 100644 --- a/components/terrain/buffercache.cpp +++ b/components/terrain/buffercache.cpp @@ -2,6 +2,8 @@ #include +#include + #include #include "defs.hpp" @@ -178,6 +180,7 @@ namespace Terrain osg::ref_ptr BufferCache::getUVBuffer() { + OpenThreads::ScopedLock lock(mUvBufferMutex); if (mUvBufferMap.find(mNumVerts) != mUvBufferMap.end()) { return mUvBufferMap[mNumVerts]; @@ -193,7 +196,7 @@ namespace Terrain for (unsigned int row = 0; row < mNumVerts; ++row) { uvs->push_back(osg::Vec2f(col / static_cast(mNumVerts-1), - row / static_cast(mNumVerts-1))); + ((mNumVerts-1) - row) / static_cast(mNumVerts-1))); } } @@ -206,6 +209,7 @@ namespace Terrain osg::ref_ptr BufferCache::getIndexBuffer(unsigned int flags) { + OpenThreads::ScopedLock lock(mIndexBufferMutex); unsigned int verts = mNumVerts; if (mIndexBufferMap.find(flags) != mIndexBufferMap.end()) @@ -215,7 +219,7 @@ namespace Terrain osg::ref_ptr buffer; - if (verts*verts > (0xffffu)) + if (verts*verts <= (0xffffu)) buffer = createIndexBuffer(flags, verts); else buffer = createIndexBuffer(flags, verts); diff --git a/components/terrain/buffercache.hpp b/components/terrain/buffercache.hpp index ca210f2380..172b9d6724 100644 --- a/components/terrain/buffercache.hpp +++ b/components/terrain/buffercache.hpp @@ -3,6 +3,7 @@ #include #include +#include #include @@ -17,8 +18,10 @@ namespace Terrain /// @param flags first 4*4 bits are LOD deltas on each edge, respectively (4 bits each) /// next 4 bits are LOD level of the index buffer (LOD 0 = don't omit any vertices) + /// @note Thread safe. osg::ref_ptr getIndexBuffer (unsigned int flags); + /// @note Thread safe. osg::ref_ptr getUVBuffer(); // TODO: add releaseGLObjects() for our vertex/element buffer objects @@ -27,8 +30,10 @@ namespace Terrain // Index buffers are shared across terrain batches where possible. There is one index buffer for each // combination of LOD deltas and index buffer LOD we may need. std::map > mIndexBufferMap; + OpenThreads::Mutex mIndexBufferMutex; std::map > mUvBufferMap; + OpenThreads::Mutex mUvBufferMutex; unsigned int mNumVerts; }; diff --git a/components/terrain/defs.hpp b/components/terrain/defs.hpp index 234e05a98a..c2342c50d2 100644 --- a/components/terrain/defs.hpp +++ b/components/terrain/defs.hpp @@ -20,6 +20,8 @@ namespace Terrain std::string mNormalMap; bool mParallax; // Height info in normal map alpha channel? bool mSpecular; // Specular info in diffuse map alpha channel? + + bool requiresShaders() const { return !mNormalMap.empty() || mSpecular; } }; } diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 59d06f2542..fa87c53ec0 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -1,30 +1,77 @@ #include "material.hpp" +#include +#include + #include #include #include #include #include -#include + +#include + namespace Terrain { - FixedFunctionTechnique::FixedFunctionTechnique(const std::vector >& layers, + osg::ref_ptr getBlendmapTexMat(int blendmapScale) + { + static std::map > texMatMap; + osg::ref_ptr texMat = texMatMap[blendmapScale]; + if (!texMat) + { + texMat = new osg::TexMat; + osg::Matrixf matrix; + float scale = (blendmapScale/(static_cast(blendmapScale)+1.f)); + matrix.preMultTranslate(osg::Vec3f(0.5f, 0.5f, 0.f)); + matrix.preMultScale(osg::Vec3f(scale, scale, 1.f)); + matrix.preMultTranslate(osg::Vec3f(-0.5f, -0.5f, 0.f)); + + texMatMap[blendmapScale] = texMat; + } + return texMat; + } + + osg::ref_ptr getLayerTexMat(float layerTileSize) + { + static std::map > texMatMap; + osg::ref_ptr texMat = texMatMap[layerTileSize]; + if (!texMat) + { + texMat = new osg::TexMat; + texMat->setMatrix(osg::Matrix::scale(osg::Vec3f(layerTileSize,layerTileSize,1.f))); + + texMatMap[layerTileSize] = texMat; + } + return texMat; + } + + osg::ref_ptr getEqualDepth() + { + static osg::ref_ptr depth; + if (!depth) + { + depth = new osg::Depth; + depth->setFunction(osg::Depth::EQUAL); + } + return depth; + } + + FixedFunctionTechnique::FixedFunctionTechnique(const std::vector& layers, const std::vector >& blendmaps, int blendmapScale, float layerTileSize) { bool firstLayer = true; int i=0; - for (std::vector >::const_iterator it = layers.begin(); it != layers.end(); ++it) + for (std::vector::const_iterator it = layers.begin(); it != layers.end(); ++it) { osg::ref_ptr stateset (new osg::StateSet); if (!firstLayer) { stateset->setMode(GL_BLEND, osg::StateAttribute::ON); - osg::ref_ptr depth (new osg::Depth); - depth->setFunction(osg::Depth::EQUAL); - stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); + + stateset->setAttributeAndModes(getEqualDepth(), osg::StateAttribute::ON); } int texunit = 0; @@ -35,17 +82,15 @@ namespace Terrain stateset->setTextureAttributeAndModes(texunit, blendmap.get()); // This is to map corner vertices directly to the center of a blendmap texel. - osg::Matrixf texMat; - float scale = (blendmapScale/(static_cast(blendmapScale)+1.f)); - texMat.preMultTranslate(osg::Vec3f(0.5f, 0.5f, 0.f)); - texMat.preMultScale(osg::Vec3f(scale, scale, 1.f)); - texMat.preMultTranslate(osg::Vec3f(-0.5f, -0.5f, 0.f)); + stateset->setTextureAttributeAndModes(texunit, getBlendmapTexMat(blendmapScale)); - stateset->setTextureAttributeAndModes(texunit, new osg::TexMat(texMat)); - - osg::ref_ptr texEnvCombine (new osg::TexEnvCombine); - texEnvCombine->setCombine_RGB(osg::TexEnvCombine::REPLACE); - texEnvCombine->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); + static osg::ref_ptr texEnvCombine; + if (!texEnvCombine) + { + texEnvCombine = new osg::TexEnvCombine; + texEnvCombine->setCombine_RGB(osg::TexEnvCombine::REPLACE); + texEnvCombine->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); + } stateset->setTextureAttributeAndModes(texunit, texEnvCombine, osg::StateAttribute::ON); @@ -53,12 +98,10 @@ namespace Terrain } // Add the actual layer texture multiplied by the alpha map. - osg::ref_ptr tex = *it; + osg::ref_ptr tex = it->mDiffuseMap; stateset->setTextureAttributeAndModes(texunit, tex.get()); - osg::ref_ptr texMat (new osg::TexMat); - texMat->setMatrix(osg::Matrix::scale(osg::Vec3f(layerTileSize,layerTileSize,1.f))); - stateset->setTextureAttributeAndModes(texunit, texMat, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(texunit, getLayerTexMat(layerTileSize), osg::StateAttribute::ON); firstLayer = false; @@ -66,23 +109,97 @@ namespace Terrain } } - Effect::Effect(const std::vector > &layers, const std::vector > &blendmaps, + ShaderTechnique::ShaderTechnique(Shader::ShaderManager& shaderManager, bool forcePerPixelLighting, bool clampLighting, const std::vector& layers, + const std::vector >& blendmaps, int blendmapScale, float layerTileSize) + { + bool firstLayer = true; + int i=0; + for (std::vector::const_iterator it = layers.begin(); it != layers.end(); ++it) + { + osg::ref_ptr stateset (new osg::StateSet); + + if (!firstLayer) + { + stateset->setMode(GL_BLEND, osg::StateAttribute::ON); + stateset->setAttributeAndModes(getEqualDepth(), osg::StateAttribute::ON); + } + + int texunit = 0; + + stateset->setTextureAttributeAndModes(texunit, it->mDiffuseMap); + + stateset->setTextureAttributeAndModes(texunit, getLayerTexMat(layerTileSize), osg::StateAttribute::ON); + + stateset->addUniform(new osg::Uniform("diffuseMap", texunit)); + + if(!firstLayer) + { + ++texunit; + osg::ref_ptr blendmap = blendmaps.at(i++); + + stateset->setTextureAttributeAndModes(texunit, blendmap.get()); + + stateset->setTextureAttributeAndModes(texunit, getBlendmapTexMat(blendmapScale)); + stateset->addUniform(new osg::Uniform("blendMap", texunit)); + } + + if (it->mNormalMap) + { + ++texunit; + stateset->setTextureAttributeAndModes(texunit, it->mNormalMap); + stateset->addUniform(new osg::Uniform("normalMap", texunit)); + } + + Shader::ShaderManager::DefineMap defineMap; + defineMap["forcePPL"] = forcePerPixelLighting ? "1" : "0"; + defineMap["clamp"] = clampLighting ? "1" : "0"; + defineMap["normalMap"] = (it->mNormalMap) ? "1" : "0"; + defineMap["blendMap"] = !firstLayer ? "1" : "0"; + defineMap["colorMode"] = "2"; + defineMap["specularMap"] = it->mSpecular ? "1" : "0"; + defineMap["parallax"] = (it->mNormalMap && it->mParallax) ? "1" : "0"; + + osg::ref_ptr vertexShader = shaderManager.getShader("terrain_vertex.glsl", defineMap, osg::Shader::VERTEX); + osg::ref_ptr fragmentShader = shaderManager.getShader("terrain_fragment.glsl", defineMap, osg::Shader::FRAGMENT); + if (!vertexShader || !fragmentShader) + throw std::runtime_error("Unable to create shader"); + + stateset->setAttributeAndModes(shaderManager.getProgram(vertexShader, fragmentShader)); + + firstLayer = false; + + addPass(stateset); + } + } + + Effect::Effect(bool useShaders, bool forcePerPixelLighting, bool clampLighting, Shader::ShaderManager* shaderManager, const std::vector &layers, const std::vector > &blendmaps, int blendmapScale, float layerTileSize) - : mLayers(layers) + : mShaderManager(shaderManager) + , mUseShaders(useShaders) + , mForcePerPixelLighting(forcePerPixelLighting) + , mClampLighting(clampLighting) + , mLayers(layers) , mBlendmaps(blendmaps) , mBlendmapScale(blendmapScale) , mLayerTileSize(layerTileSize) { - osg::ref_ptr material (new osg::Material); - material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); - getOrCreateStateSet()->setAttributeAndModes(material, osg::StateAttribute::ON); - selectTechnique(0); } bool Effect::define_techniques() { - addTechnique(new FixedFunctionTechnique(mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize)); + try + { + if (mUseShaders && mShaderManager) + addTechnique(new ShaderTechnique(*mShaderManager, mForcePerPixelLighting, mClampLighting, mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize)); + else + addTechnique(new FixedFunctionTechnique(mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize)); + } + catch (std::exception& e) + { + std::cerr << "Error: " << e.what() << std::endl; + addTechnique(new FixedFunctionTechnique(mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize)); + } return true; } diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp index dd00e41ed4..c13b21e8fc 100644 --- a/components/terrain/material.hpp +++ b/components/terrain/material.hpp @@ -11,14 +11,38 @@ namespace osg class Texture2D; } +namespace Shader +{ + class ShaderManager; +} + namespace Terrain { + struct TextureLayer + { + osg::ref_ptr mDiffuseMap; + osg::ref_ptr mNormalMap; // optional + bool mParallax; + bool mSpecular; + }; + class FixedFunctionTechnique : public osgFX::Technique { public: FixedFunctionTechnique( - const std::vector >& layers, + const std::vector& layers, + const std::vector >& blendmaps, int blendmapScale, float layerTileSize); + + protected: + virtual void define_passes() {} + }; + + class ShaderTechnique : public osgFX::Technique + { + public: + ShaderTechnique(Shader::ShaderManager& shaderManager, bool forcePerPixelLighting, bool clampLighting, + const std::vector& layers, const std::vector >& blendmaps, int blendmapScale, float layerTileSize); protected: @@ -28,8 +52,8 @@ namespace Terrain class Effect : public osgFX::Effect { public: - Effect( - const std::vector >& layers, + Effect(bool useShaders, bool forcePerPixelLighting, bool clampLighting, Shader::ShaderManager* shaderManager, + const std::vector& layers, const std::vector >& blendmaps, int blendmapScale, float layerTileSize); virtual bool define_techniques(); @@ -48,7 +72,11 @@ namespace Terrain } private: - std::vector > mLayers; + Shader::ShaderManager* mShaderManager; + bool mUseShaders; + bool mForcePerPixelLighting; + bool mClampLighting; + std::vector mLayers; std::vector > mBlendmaps; int mBlendmapScale; float mLayerTileSize; diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index aa82e0bd65..231634e1ea 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -2,18 +2,22 @@ #include +#include + +#include + #include -#include +#include +#include #include #include +#include #include #include -#include #include -#include #include @@ -45,13 +49,16 @@ namespace namespace Terrain { -TerrainGrid::TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, - Storage* storage, int nodeMask) +TerrainGrid::TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, Storage* storage, int nodeMask, Shader::ShaderManager* shaderManager, SceneUtil::UnrefQueue* unrefQueue) : Terrain::World(parent, resourceSystem, ico, storage, nodeMask) , mNumSplits(4) - , mKdTreeBuilder(new osg::KdTreeBuilder) + , mCache((storage->getCellVertices()-1)/static_cast(mNumSplits) + 1) + , mUnrefQueue(unrefQueue) + , mShaderManager(shaderManager) { - mCache = BufferCache((storage->getCellVertices()-1)/static_cast(mNumSplits) + 1); + osg::ref_ptr material (new osg::Material); + material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); + mTerrainRoot->getOrCreateStateSet()->setAttributeAndModes(material, osg::StateAttribute::ON); } TerrainGrid::~TerrainGrid() @@ -62,11 +69,20 @@ TerrainGrid::~TerrainGrid() } } -class GridElement +osg::ref_ptr TerrainGrid::cacheCell(int x, int y) { -public: - osg::ref_ptr mNode; -}; + { + OpenThreads::ScopedLock lock(mGridCacheMutex); + Grid::iterator found = mGridCache.find(std::make_pair(x,y)); + if (found != mGridCache.end()) + return found->second; + } + osg::ref_ptr node = buildTerrain(NULL, 1.f, osg::Vec2f(x+0.5, y+0.5)); + + OpenThreads::ScopedLock lock(mGridCacheMutex); + mGridCache.insert(std::make_pair(std::make_pair(x,y), node)); + return node; +} osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter) { @@ -133,12 +149,51 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu // For compiling textures, I don't think the osgFX::Effect does it correctly osg::ref_ptr textureCompileDummy (new osg::Node); + unsigned int dummyTextureCounter = 0; - std::vector > layerTextures; - for (std::vector::const_iterator it = layerList.begin(); it != layerList.end(); ++it) + bool useShaders = mResourceSystem->getSceneManager()->getForceShaders(); + if (!mResourceSystem->getSceneManager()->getClampLighting()) + useShaders = true; // always use shaders when lighting is unclamped, this is to avoid lighting seams between a terrain chunk with normal maps and one without normal maps + std::vector layers; { - layerTextures.push_back(mResourceSystem->getTextureManager()->getTexture2D(it->mDiffuseMap, osg::Texture::REPEAT, osg::Texture::REPEAT)); - textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(0, layerTextures.back()); + OpenThreads::ScopedLock lock(mTextureCacheMutex); + for (std::vector::const_iterator it = layerList.begin(); it != layerList.end(); ++it) + { + TextureLayer textureLayer; + textureLayer.mParallax = it->mParallax; + textureLayer.mSpecular = it->mSpecular; + osg::ref_ptr texture = mTextureCache[it->mDiffuseMap]; + if (!texture) + { + texture = new osg::Texture2D(mResourceSystem->getImageManager()->getImage(it->mDiffuseMap)); + texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); + texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); + mResourceSystem->getSceneManager()->applyFilterSettings(texture); + mTextureCache[it->mDiffuseMap] = texture; + } + textureLayer.mDiffuseMap = texture; + textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, texture); + + if (!it->mNormalMap.empty()) + { + texture = mTextureCache[it->mNormalMap]; + if (!texture) + { + texture = new osg::Texture2D(mResourceSystem->getImageManager()->getImage(it->mNormalMap)); + texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); + texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); + mResourceSystem->getSceneManager()->applyFilterSettings(texture); + mTextureCache[it->mNormalMap] = texture; + } + textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, texture); + textureLayer.mNormalMap = texture; + } + + if (it->requiresShaders()) + useShaders = true; + + layers.push_back(textureLayer); + } } std::vector > blendmapTextures; @@ -148,12 +203,10 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu texture->setImage(*it); texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); - texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); texture->setResizeNonPowerOfTwoHint(false); blendmapTextures.push_back(texture); - textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(0, layerTextures.back()); + textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, blendmapTextures.back()); } // use texture coordinates for both texture units, the layer texture and blend texture @@ -161,19 +214,14 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu geometry->setTexCoordArray(i, mCache.getUVBuffer()); float blendmapScale = ESM::Land::LAND_TEXTURE_SIZE*chunkSize; - osg::ref_ptr effect (new Terrain::Effect(layerTextures, blendmapTextures, blendmapScale, blendmapScale)); + osg::ref_ptr effect (new Terrain::Effect(mShaderManager ? useShaders : false, mResourceSystem->getSceneManager()->getForcePerPixelLighting(), mResourceSystem->getSceneManager()->getClampLighting(), + mShaderManager, layers, blendmapTextures, blendmapScale, blendmapScale)); effect->addCullCallback(new SceneUtil::LightListCallback); transform->addChild(effect); -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) osg::Node* toAttach = geometry.get(); -#else - osg::ref_ptr geode (new osg::Geode); - geode->addDrawable(geometry); - osg::Node* toAttach = geode.get(); -#endif effect->addChild(toAttach); @@ -192,24 +240,31 @@ void TerrainGrid::loadCell(int x, int y) if (mGrid.find(std::make_pair(x, y)) != mGrid.end()) return; // already loaded - osg::Vec2f center(x+0.5f, y+0.5f); + // try to get it from the cache + osg::ref_ptr terrainNode; + { + OpenThreads::ScopedLock lock(mGridCacheMutex); + Grid::const_iterator found = mGridCache.find(std::make_pair(x,y)); + if (found != mGridCache.end()) + { + terrainNode = found->second; + if (!terrainNode) + return; // no terrain defined + } + } - osg::ref_ptr terrainNode = buildTerrain(NULL, 1.f, center); + // didn't find in cache, build it if (!terrainNode) - return; // no terrain defined + { + osg::Vec2f center(x+0.5f, y+0.5f); + terrainNode = buildTerrain(NULL, 1.f, center); + if (!terrainNode) + return; // no terrain defined + } - std::auto_ptr element (new GridElement); - element->mNode = terrainNode; - mTerrainRoot->addChild(element->mNode); + mTerrainRoot->addChild(terrainNode); - // kdtree probably not needed with mNumSplits=4 - /* - // build a kdtree to speed up intersection tests with the terrain - // Note, the build could be optimized using a custom kdtree builder, since we know that the terrain can be represented by a quadtree - geode->accept(*mKdTreeBuilder); - */ - - mGrid[std::make_pair(x,y)] = element.release(); + mGrid[std::make_pair(x,y)] = terrainNode; } void TerrainGrid::unloadCell(int x, int y) @@ -218,11 +273,45 @@ void TerrainGrid::unloadCell(int x, int y) if (it == mGrid.end()) return; - GridElement* element = it->second; - mTerrainRoot->removeChild(element->mNode); - delete element; + osg::ref_ptr terrainNode = it->second; + mTerrainRoot->removeChild(terrainNode); + + if (mUnrefQueue.get()) + mUnrefQueue->push(terrainNode); mGrid.erase(it); } +void TerrainGrid::updateCache() +{ + { + OpenThreads::ScopedLock lock(mGridCacheMutex); + for (Grid::iterator it = mGridCache.begin(); it != mGridCache.end();) + { + if (it->second->referenceCount() <= 1) + mGridCache.erase(it++); + else + ++it; + } + } + + { + OpenThreads::ScopedLock lock(mTextureCacheMutex); + for (TextureCache::iterator it = mTextureCache.begin(); it != mTextureCache.end();) + { + if (it->second->referenceCount() <= 1) + mTextureCache.erase(it++); + else + ++it; + } + } +} + +void TerrainGrid::updateTextureFiltering() +{ + OpenThreads::ScopedLock lock(mTextureCacheMutex); + for (TextureCache::iterator it = mTextureCache.begin(); it != mTextureCache.end(); ++it) + mResourceSystem->getSceneManager()->applyFilterSettings(it->second); +} + } diff --git a/components/terrain/terraingrid.hpp b/components/terrain/terraingrid.hpp index 169a9a6228..defcce8b3f 100644 --- a/components/terrain/terraingrid.hpp +++ b/components/terrain/terraingrid.hpp @@ -4,39 +4,72 @@ #include #include "world.hpp" -#include "material.hpp" + +namespace SceneUtil +{ + class UnrefQueue; +} + +namespace Shader +{ + class ShaderManager; +} namespace osg { - class KdTreeBuilder; + class Texture2D; } namespace Terrain { - class GridElement; - /// @brief Simple terrain implementation that loads cells in a grid, with no LOD class TerrainGrid : public Terrain::World { public: - TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, - Storage* storage, int nodeMask); + TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, Storage* storage, int nodeMask, Shader::ShaderManager* shaderManager = NULL, SceneUtil::UnrefQueue* unrefQueue = NULL); ~TerrainGrid(); + /// Load a terrain cell and store it in cache for later use. + /// @note The returned ref_ptr should be kept by the caller to ensure that the terrain stays in cache for as long as needed. + /// @note Thread safe. + virtual osg::ref_ptr cacheCell(int x, int y); + + /// @note Not thread safe. virtual void loadCell(int x, int y); + + /// @note Not thread safe. virtual void unloadCell(int x, int y); + /// Clear cached objects that are no longer referenced + /// @note Thread safe. + void updateCache(); + + /// Apply the scene manager's texture filtering settings to all cached textures. + /// @note Thread safe. + void updateTextureFiltering(); + private: osg::ref_ptr buildTerrain (osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter); // split each ESM::Cell into mNumSplits*mNumSplits terrain chunks unsigned int mNumSplits; - typedef std::map, GridElement*> Grid; + typedef std::map > TextureCache; + TextureCache mTextureCache; + OpenThreads::Mutex mTextureCacheMutex; + + typedef std::map, osg::ref_ptr > Grid; Grid mGrid; - osg::ref_ptr mKdTreeBuilder; + Grid mGridCache; + OpenThreads::Mutex mGridCacheMutex; + + BufferCache mCache; + + osg::ref_ptr mUnrefQueue; + + Shader::ShaderManager* mShaderManager; }; } diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index 2250b593d2..b56e87e1d5 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -11,7 +11,6 @@ namespace Terrain World::World(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, Storage* storage, int nodeMask) : mStorage(storage) - , mCache(storage->getCellVertices()) , mParent(parent) , mResourceSystem(resourceSystem) , mIncrementalCompileOperation(ico) diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index 4212f2a0c2..f5638750cd 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -39,8 +39,14 @@ namespace Terrain Storage* storage, int nodeMask); virtual ~World(); + virtual void updateTextureFiltering() {} + + virtual void updateCache() {} + float getHeightAt (const osg::Vec3f& worldPos); + virtual osg::ref_ptr cacheCell(int x, int y) {return NULL;} + // This is only a hint and may be ignored by the implementation. virtual void loadCell(int x, int y) {} virtual void unloadCell(int x, int y) {} @@ -50,8 +56,6 @@ namespace Terrain protected: Storage* mStorage; - BufferCache mCache; - osg::ref_ptr mParent; osg::ref_ptr mTerrainRoot; diff --git a/components/vfs/bsaarchive.hpp b/components/vfs/bsaarchive.hpp index 25ad60e0a6..a6e2740378 100644 --- a/components/vfs/bsaarchive.hpp +++ b/components/vfs/bsaarchive.hpp @@ -34,4 +34,4 @@ namespace VFS } -#endif \ No newline at end of file +#endif diff --git a/components/vfs/manager.hpp b/components/vfs/manager.hpp index f74914977a..6592a65a88 100644 --- a/components/vfs/manager.hpp +++ b/components/vfs/manager.hpp @@ -16,6 +16,7 @@ namespace VFS /// @par Various archive types (e.g. directories on the filesystem, or compressed archives) /// can be registered, and will be merged into a single file tree. If the same filename is /// contained in multiple archives, the last added archive will have priority. + /// @par Most of the methods in this class are considered thread-safe, see each method documentation for details. class Manager { public: @@ -33,20 +34,25 @@ namespace VFS void buildIndex(); /// Does a file with this name exist? + /// @note May be called from any thread once the index has been built. bool exists(const std::string& name) const; /// Get a complete list of files from all archives + /// @note May be called from any thread once the index has been built. const std::map& getIndex() const; /// Normalize the given filename, making slashes/backslashes consistent, and lower-casing if mStrict is false. + /// @note May be called from any thread once the index has been built. void normalizeFilename(std::string& name) const; /// Retrieve a file by name. /// @note Throws an exception if the file can not be found. + /// @note May be called from any thread once the index has been built. Files::IStreamPtr get(const std::string& name) const; /// Retrieve a file by name (name is already normalized). /// @note Throws an exception if the file can not be found. + /// @note May be called from any thread once the index has been built. Files::IStreamPtr getNormalized(const std::string& normalizedName) const; private: diff --git a/docs/cs-manual/Makefile b/docs/cs-manual/Makefile new file mode 100644 index 0000000000..9d62dc5abb --- /dev/null +++ b/docs/cs-manual/Makefile @@ -0,0 +1,216 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source + +.PHONY: help +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + +.PHONY: clean +clean: + rm -rf $(BUILDDIR)/* + +.PHONY: html +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +.PHONY: dirhtml +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +.PHONY: singlehtml +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +.PHONY: pickle +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +.PHONY: json +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +.PHONY: htmlhelp +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +.PHONY: qthelp +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/OpenMWCSManual.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/OpenMWCSManual.qhc" + +.PHONY: applehelp +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +.PHONY: devhelp +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/OpenMWCSManual" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/OpenMWCSManual" + @echo "# devhelp" + +.PHONY: epub +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +.PHONY: latex +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +.PHONY: latexpdf +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: latexpdfja +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: text +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +.PHONY: man +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +.PHONY: texinfo +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +.PHONY: info +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +.PHONY: gettext +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +.PHONY: changes +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +.PHONY: linkcheck +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +.PHONY: doctest +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +.PHONY: coverage +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +.PHONY: xml +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +.PHONY: pseudoxml +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/cs-manual/make.bat b/docs/cs-manual/make.bat new file mode 100644 index 0000000000..744d600076 --- /dev/null +++ b/docs/cs-manual/make.bat @@ -0,0 +1,263 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source +set I18NSPHINXOPTS=%SPHINXOPTS% source +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + echo. coverage to run coverage check of the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +REM Check if sphinx-build is available and fallback to Python version if any +%SPHINXBUILD% 1>NUL 2>NUL +if errorlevel 9009 goto sphinx_python +goto sphinx_ok + +:sphinx_python + +set SPHINXBUILD=python -m sphinx.__init__ +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +:sphinx_ok + + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\OpenMWCSManual.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\OpenMWCSManual.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "coverage" ( + %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage + if errorlevel 1 exit /b 1 + echo. + echo.Testing of coverage in the sources finished, look at the ^ +results in %BUILDDIR%/coverage/python.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end diff --git a/docs/cs-manual/source/_static/images/chapter-1/add-record.png b/docs/cs-manual/source/_static/images/chapter-1/add-record.png new file mode 100644 index 0000000000..c0a110b7c5 Binary files /dev/null and b/docs/cs-manual/source/_static/images/chapter-1/add-record.png differ diff --git a/docs/cs-manual/source/_static/images/chapter-1/edit-record.png b/docs/cs-manual/source/_static/images/chapter-1/edit-record.png new file mode 100644 index 0000000000..4db3d1bd67 Binary files /dev/null and b/docs/cs-manual/source/_static/images/chapter-1/edit-record.png differ diff --git a/docs/cs-manual/source/_static/images/chapter-1/new-project.png b/docs/cs-manual/source/_static/images/chapter-1/new-project.png new file mode 100644 index 0000000000..a5097db9c0 Binary files /dev/null and b/docs/cs-manual/source/_static/images/chapter-1/new-project.png differ diff --git a/docs/cs-manual/source/_static/images/chapter-1/objects.png b/docs/cs-manual/source/_static/images/chapter-1/objects.png new file mode 100644 index 0000000000..f36a7289ca Binary files /dev/null and b/docs/cs-manual/source/_static/images/chapter-1/objects.png differ diff --git a/docs/cs-manual/source/_static/images/chapter-1/opening-dialogue.png b/docs/cs-manual/source/_static/images/chapter-1/opening-dialogue.png new file mode 100644 index 0000000000..5356c26956 Binary files /dev/null and b/docs/cs-manual/source/_static/images/chapter-1/opening-dialogue.png differ diff --git a/docs/cs-manual/source/conf.py b/docs/cs-manual/source/conf.py new file mode 100644 index 0000000000..28931a4a0d --- /dev/null +++ b/docs/cs-manual/source/conf.py @@ -0,0 +1,358 @@ +# -*- coding: utf-8 -*- +# +# OpenMW CS Manual documentation build configuration file, created by +# sphinx-quickstart on Fri Feb 5 21:28:27 2016. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'OpenMW CS Manual' +copyright = u'2016, The OpenMW Project' +author = u'HiPhish' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = u'0.0' +# The full version, including alpha/beta/rc tags. +release = u'0.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'alabaster' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' +#html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +#html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +#html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'OpenMWCSManualdoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', + +# Latex figure (float) alignment +#'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'OpenMWCSManual.tex', u'OpenMW CS Manual Documentation', + u'HiPhish', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'openmwcsmanual', u'OpenMW CS Manual Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'OpenMWCSManual', u'OpenMW CS Manual Documentation', + author, 'OpenMWCSManual', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False + + +# -- Options for Epub output ---------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project +epub_author = author +epub_publisher = author +epub_copyright = copyright + +# The basename for the epub file. It defaults to the project name. +#epub_basename = project + +# The HTML theme for the epub output. Since the default themes are not +# optimized for small screen space, using the same theme for HTML and epub +# output is usually not wise. This defaults to 'epub', a theme designed to save +# visual space. +#epub_theme = 'epub' + +# The language of the text. It defaults to the language option +# or 'en' if the language is not set. +#epub_language = '' + +# The scheme of the identifier. Typical schemes are ISBN or URL. +#epub_scheme = '' + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +#epub_identifier = '' + +# A unique identification for the text. +#epub_uid = '' + +# A tuple containing the cover image and cover page html template filenames. +#epub_cover = () + +# A sequence of (type, uri, title) tuples for the guide element of content.opf. +#epub_guide = () + +# HTML files that should be inserted before the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_pre_files = [] + +# HTML files that should be inserted after the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_post_files = [] + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ['search.html'] + +# The depth of the table of contents in toc.ncx. +#epub_tocdepth = 3 + +# Allow duplicate toc entries. +#epub_tocdup = True + +# Choose between 'default' and 'includehidden'. +#epub_tocscope = 'default' + +# Fix unsupported image types using the Pillow. +#epub_fix_images = False + +# Scale large images. +#epub_max_image_width = 0 + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#epub_show_urls = 'inline' + +# If false, no index is generated. +#epub_use_index = True + + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'https://docs.python.org/': None} diff --git a/docs/cs-manual/source/files-and-directories.rst b/docs/cs-manual/source/files-and-directories.rst new file mode 100644 index 0000000000..34680fa945 --- /dev/null +++ b/docs/cs-manual/source/files-and-directories.rst @@ -0,0 +1,224 @@ +Files and Directories +##################### + +In this chapter of the manual we will cover the usage of files and directories +by OpenMW CS. Files and directories are file system concepts of your operating +system, so we will not be going into specifics about that, we will only focus +on what is relevant to OpenMW CS. + + +Basics +****** + + +Directories +=========== + +OpenMW and OpenMW CS us multiple directories on the file system. First of all +there is a *user directory* that holds configuration files and a number of +different sub-directories. The location of the user directory is hard-coded +into the CS and depends on your operating system. + +================ ========================================= +Operating System User Dircetory +================ ========================================= +GNU/Linux ```` +OS X ``~/Library/Application Support/openmw/`` +Windows ```` +================ ========================================= + +In addition to to this single hard-coded directory both OpenMW and OpenMW CS +need a place to seek for a actuals data files of the game: textures, 3D models, +sounds and record files that store objects in game; dialogues an so one. These +files are called *content files*. We support multiple such paths (we call them +*data paths*) as specified in the configuration. Usually one data path points +to the directory where the original Morrowind game is either installed or +unpacked to. You are free to specify as many data paths as you would like, +however, there is one special data path that, as described later, which is used +to store newly created content files. + + +Content files +============= + +The original Morrowind engine by Bethesda Softworks uses two types of content +files: `esm` (master) and `esp` (plugin). The distinction between those two is +not clear, and often confusing. One would expect the `esm` (master) file to be +used to specify one master, which is then modified by the `esp` plugins. And +indeed: this is the basic idea. However, the official expansions were also made +as ESM files, even though they could essentially be described as really large +plugins, and therefore would rather use `esp` files. There were technical +reasons behind this decision – somewhat valid in the case of the original +engine, but clearly it is better to create a system that can be used in a more +sensible way. OpenMW achieves this with our own content file types. + +We support both ESM and ESP files, but in order to make use of new features in +OpenMW one should consider using new file types designed with our engine in +mind: *game* files and *addon* files, collectively called *content files*. + + +OpenMW content files +-------------------- + +The concepts of *Game* and *Addon* files are somewhat similar to the old +concept of *ESM* and *ESP*, but more strictly enforced. It is quite +straight-formward: If you want to make new game using OpenMW as the engine (a +so called *total conversion*) you should create a game file. If you want to +create an addon for an existing game file create an addon file. Nothing else +matters; the only distinction you should consider is if your project is about +changing another game or creating a new one. Simple as that. + +Another simple thing about content files are the extensions: we are using +``.omwaddon`` for addon files and ``.omwgame`` for game files. + + +Morrowind content files +----------------------- + +Using our content files is recommended for projects that are intended to used +with the OpenMW engine. However, some players might wish to still use the +original Morrowind engine. In addition thousands of *ESP*/*ESM* files were +created since 2002, some of them with really outstanding content. Because of +this OpenMW CS simply has no other choice but to support *ESP*/*ESM* files. If +you decid to choose *ESP*/*ESM* file instead of using our own content file +types you are most likely aimng at compatibility with the original engine. This +subject is covered in it own chapter of this manual. + + +.. TODO This paragraph sounds weird + +The actual creation of new files is described in the next chapter. Here we are +going to focus only on the details you need to know in order to create your +first OpenMW CS file while fully understanding your needs. For now let’s jut +remember that content files are created inside the user directory in the the +``data`` subdirectory (that is the one special data directory mentioned +earlier). + + +Dependencies +------------ + +Since an addon is supposed to change the game it follows that it also depends +on the said game to work. We can conceptualise this with an examples: your +modification is the changing prize of an iron sword, but what if there is no +iron sword in game? That's right: we get nonsense. What you want to do is tie +your addon to the files you are changing. Those can be either game files (for +example when making an expansion island for a game) or other addon files +(making a house on said island). Obviously It is a good idea to be dependent +only on files that are really changed in your addon, but sadly there is no +other way to achieve this than knowing what you want to do. Again, please +remember that this section of the manual does not cover creating the content +files – it is only a theoretical introduction to the subject. For now just keep +in mind that dependencies exist, and is up to you to decide whether your +content file should depend on other content files. + +Game files are not intend to have any dependencies for a very simple reasons: +the player is using only one game file (excluding original and the dirty +ESP/ESM system) at a time and therefore no game file can depend on other game +file, and since a game file makes the base for addon files it can not depend on +addon files. + + +Project files +------------- + +Project files act as containers for data not used by the OpenMW game engine +itself, but still useful for OpenMW CS. The shining example of this data +category are without doubt record filters (described in a later chapter of the +manual). As a mod author you probably do not need or want to distribute project +files at all, they are meant to be used only by you and your team. + +.. TODO "you and your team": is that correct? + +As you would imagine, project files make sense only in combination with actual +content files. In fact, each time you start to work on new content file and a +project file was not found, one will be created. The extensio of project files +is ``.project``. The whole name of the project file is the whole name of the +content file with appended extension. For instance a ``swords.omwaddon`` file +is associated with a ``swords.omwaddon.project`` file. + +Project files are stored inside the user directory, in the ``projects`` +subdirectory. This is the path location for both freshly created project files, +and a place where OpenMW CS looks for already existing files. + + +Resource files +============== + +.. TODO This paragraph sounds weird + +Unless we are talking about a fully text based game, like Zork or Rogue, one +would expect that a video game is using some media files: 3D models with +textures, images acting as icons, sounds and anything else. Since content +files, no matter whether they are *ESP*, *ESM* or new OpenMW file type, do not +contain any of those, it is clear that they have to be delivered with a +different file. It is also clear that this, let’s call it “resources file“, +has to be supported by the engine. Without code handling those files it is +nothing more than a mathematical abstraction – something, that lacks meaning +for human beings. Therefore this section must cover ways to add resources +files to your content file, and point out what is supported. We are going to do +just that. Later, you will learn how to make use of those files in your +content. + + +Audio +----- + +OpenMW uses FFmpeg_ for audio playback, and so we support every audio type +supported by that library. This makes a huge list. Below is only small portion +of the supported file types. + +mp3 (MPEG-1 Part 3 Layer 3) + A popular audio file format and de facto standard for storing audio. Used by + the Morrowind game. + +ogg + An open source, multimedia container file using a high quality Vorbis_ audio + codec. Recommended. + + +Video +----- + +Video As in the case of audio files, we are using FFmepg to decode video files. +The list of supported files is long, we will cover only the most significant. + +bik + Videos used by the original Morrowind game. + +mp4 + A multimedia container which use more advanced codecs (MPEG-4 Parts 2, 3 and + 10) with a better audio and video compression rate, but also requiring more + CPU intensive decoding – this makes it probably less suited for storing + sounds in computer games, but good for videos. + +webm + A new, shiny and open source video format with excellent compression. It + needs quite a lot of processing power to be decoded, but since game logic is + not running during cutscenes we can recommend it for use with OpenMW. + +ogv + Alternative, open source container using Theora_ codec for video and Vorbis for audio. + + + +Textures and images +------------------- + +The original Morrowind game uses *DDS* and *TGA* files for all kinds of two +dimensional images and textures alike. In addition, the engine supported *BMP* +files for some reason (*BMP* is a terrible format for a video game). We also +support an extended set of image files – including *JPEG* and *PNG*. *JPEG* and +*PNG* files can be useful in some cases, for instance a *JPEG* file is a valid +option for skybox texture and *PNG* can useful for masks. However, please keep +in mind that *JPEG* images can grow to large sizes quickly and are not the best +option with a DirectX rendering backend. You probably still want to use *DDS* +files for textures. + + + +.. Hyperlink targets for the entire document + +.. _FFmpeg: http://ffmpeg.org +.. _Vorbis: http://www.vorbis.com +.. _Theora: http://www.theora.org diff --git a/docs/cs-manual/source/foreword.rst b/docs/cs-manual/source/foreword.rst new file mode 100644 index 0000000000..613eca5e34 --- /dev/null +++ b/docs/cs-manual/source/foreword.rst @@ -0,0 +1,21 @@ +Foreword +######## + + + + +How to read the manual +********************** + +The manual can be roughly divided into two parts: a tutorial part consisting of +the first two (three) chapters and the reference manual. We recommend all +readers to work through the tutorials first, there you will be guided through +the creation of a fairly simple mod where you can familiarise yourself with the +record-based interface. The tutorials are very simple and teach you only what +is necessary for the task, each one can be completed in under half an hour. It +is strongly recommended to do the tutorials in order. + +When you are familiar with the CS in general and want to write your own mods it +is time to move on to the reference part of the manual. The reference chapters +can be read out of order, each one covers only one topic. + diff --git a/docs/cs-manual/source/index.rst b/docs/cs-manual/source/index.rst new file mode 100644 index 0000000000..ce50b8c95a --- /dev/null +++ b/docs/cs-manual/source/index.rst @@ -0,0 +1,30 @@ +.. OpenMW CS Manual documentation master file, created by + sphinx-quickstart on Fri Feb 5 21:28:27 2016. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +##################### +OpenMW CS user manual +##################### + +The following document is the complete user manual for *OpenMW CS*, the +construction set for the OpenMW game engine. It is intended to serve both as an +introduction and a reference for the application. Even if you are familiar with +modding *The Elder Scrolls III: Morrowind* you should at least read the first +few chapters to familiarise yourself with the new interface. + +.. warning:: + OpenMW CS is still software in development. The manual does not cover any of + its shortcomings, it is written as if everything was working as inteded. + Please report any software problems as bugs in the software, not errors in + the manual. + +.. toctree:: + :caption: Table of Contents + :maxdepth: 2 + + foreword + tour + files-and-directories + starting-dialog + diff --git a/docs/cs-manual/source/starting-dialog.rst b/docs/cs-manual/source/starting-dialog.rst new file mode 100644 index 0000000000..02a65ff213 --- /dev/null +++ b/docs/cs-manual/source/starting-dialog.rst @@ -0,0 +1,40 @@ +OpenMW CS Starting Dialog +######################### + +In this chapter we will cover starting up OpenMW CS and the starting interface. +Start the CS the way intended for your operating system and you will be +presented with window and three main buttons and a small button with a +wrench-icon. The wrench will open the configuration dialog which we will cover +later. The three main buttons are the following: + +Create A New Game + Choose this option if you want to create an original game that does not + depend on any other content files. The distinction between game and addon in + the original Morrowind engine was somewhat blurry, but OpenMW is very strict + about it: regardless of how large your addon is, if it depends on another + content file it is not an original game. + +Create A New Addon + Choose this option if you want to create an extension to an existing game. + An addon can depend on other addons as well optionally, but it *must* depend + on a game. + +Edit A Content File + Choose this option is you wish to edit an existing content file, regardless + of whether it is a game or an addon. + +Whether you create a game or an addon, a data file and a project file will be +generated for you in you user directory. + +You will have to choose a name for your content file and if you chose to create +an addon you will also have to chose a number of dependencies. You have to +choose exactly one game and you can choose an arbitrary amount of addon +dependencies. For the sake of simplicity and maintainability choose only the +addons you actually want to depend on. Also keep in mind that your dependencies +might have dependencies of their own, you have to depend on those as well. If +one of your dependencies nees something it will be indicated by a warning sign +and automatically include its dependencies when you choose it. + +If you want to edit an existing content file you will be presented with a +similar dialog, except you don't get to choose a file name (because you are +editing files that already exist). diff --git a/docs/cs-manual/source/tour.rst b/docs/cs-manual/source/tour.rst new file mode 100644 index 0000000000..0e92fe6faa --- /dev/null +++ b/docs/cs-manual/source/tour.rst @@ -0,0 +1,223 @@ +A Tour through OpenMW CS: making a magic ring +############################################# + +In this first chapter we will create a mod that adds a new ring with a simple +enchantment to the game. The ring will give its wearer a permanent Night Vision +effect while being worn. You don't need prior knowledge about modding +Morrowind, but you should be familiar with the game itself. There will be no +scripting necessary, we chan achieve everything using just what the base game +offers out of the box. Before continuing make sure that OpenMW is properly +installed and playable. + + +Adding the ring to the game's records +************************************* + +In this first section we will define what our new ring is, what it looks like +and what it does. Getting it to work is the first step before we go further. + + +Starting up OpenMW CS +===================== + +We will start by launching OpenMW CS, the location of the program depends on +your operating system. You will be presented with a dialogue with three +options: create a new game, create a new addon, edit a content file. + +.. figure:: ./_static/images/chapter-1/opening-dialogue.png + :alt: Opening dialogue with three option and setting button (the wrench) + +The first option is for creating an entirely new game, that's not what we want. +We want to edit an existing game, so choose the second one. When you save your +addon you can use the third option to open it again. + +You will be presented with another window where you get to chose the content to +edit and the name of your project. We have to chose at least a base game, and +optionally a number of other addons we want to depend on. The name of the +project is arbitrary, it will be used to identify the addon later in the OpenMW +launcher. + +.. figure:: ./_static/images/chapter-1/new-project.png + :alt: Creation dialogue for a new project, pick content modules and name + +Choose Morrowind as your content file and enter `Ring of Night Vision` as the +name. We could also chose further content files as dependencies if we wanted +to, but for this mod the base game is enough. + +Once the addon has been created you will be presented with a table. If you see +a blank window rather than a table choose *World* → *Objects* from the menu. + +.. figure:: ./_static/images/chapter-1/objects.png + :alt: The table showing all objet records in the game. + +Let's talk about the interface for a second. Every window in OpenMW CS has +*panels*, these are often but not always tables. You can close a panel by +clicking the small "X" on the title bar of the panel, or you can detach it by +either dragging the title bar or clicking the icon with the two windows. A +detached panel can be re-attached to a window by dragging it by the title bar +on top of the window. + +Now let's look at the panel itself: we have a filter text field, a very large +table and a status bar. The filter will be very useful when we want to find an +entry in the table, but for now it is irrelevant. The table you are looking at +contains all objects in the game, these can be items, NPCs, creatures, +whatever. Every object is an entry in that table, visible as a row. The columns +of the table are the attributes of each object. + +Morrowind uses something called a *relational database* for game data. If you +are not familiar with the term, it means that every type of thing can be +expressed as a *table*: there is a table for objects, a table for enchantments, +a table for icons, one for meshes, and so on. Properties of an entry must be +simple values, like numbers or text strings. If we want a more complicated +property we need to reference an entry from another table. There are a few +exceptions to this though, some tables do have subtables. The effects of +enchantments are one of those exceptions. + + +Defining a new record +===================== + +Enough talk, let's create the new ring now. Right-click anywhere in the objects +table, choose `Add Record` and the status bar will change into an input field. +We need to enter an *ID* (short for *identifier*) and pick the type. The +identifier is a unique name by which the ring can later be identified; I have +chosen `ring_night_vision`. For the type choose *Clothing*. + +.. figure:: ./_static/images/chapter-1/add-record.png + :alt: Enter the ID and type of the new ring + +The table should jump right to our newly created record, if not read further +below how to use filters to find a record by ID. Notice that the *Modified* +column now shows that this record is new. Records can also be *Base* +(unmodified), *Modified* and *Deleted*. The other fields are still empty since +we created this record from nothing. We can double-click a table cell while +holding Shift to edit it (this is a configurable shortcut), but there is a +better way: right-click the row of our new record and chose *Edit Record*, a +new panel will open. + +We can right-click the row of our new record and chose *Edit Record*, a +new panel will open. Alternatively we can also define a configurable shortcut +instead of using the context menu; the default is double-clicking while +holding down the shift key. + + +.. figure:: ./_static/images/chapter-1/edit-record.png + :alt: Edit the properties of the record in a separate panel + +You can set the name, weight and coin value as you like, I chose `Ring of Night +Vision`, `0.1` and `2500` respectively. Make sure you set the *Clothing Type* +to *Ring*. We could set the other properties manually as well, but unless you +have an exceptional memory for identifiers and never make typos that's not +feasible. What we are going to do instead is find the records we want in their +respective tables and assign them from there. + + +Finding records using filters +============================= + +We will add an icon first. Open the *Icons* table the same way you opened the +*Objects* table: in the menu click *Assets* → *Icons*. If the window gets too +crowded remember that you can detach panels. The table is huge and not every +ring icon starts with "ring", so we have to use filters to find what we want. + +Filters are a central element of OpenMW CS and a major departure from how the +original Morrowind CS was used. In fact, filters are so important that they +have their own table as well. We won't be going that far for now though. There +are three types of filters: *Project filters* are part of the project and are +stored in the project file, *session filter* are only valid until you exit the +CS, and finally *instant filter* which are used only once and typed directly +into the *Filter* field. + +For this tutorial we will only use instant filters. We type the definition of +the filter directly into the filter field rather than the name of an existing +filter. To signify that we are using an instant filter the have to use `!` as +the first character. Type the following into the field: + +.. code:: + + !string("id", ".*ring.*") + +A filter is defined by a number of *queries* which can be logically linked. For +now all that matters is that the `string(, )` query will check +whether `` matches ``. The pattern is a regular expression, +if you don't know about them you should learn their syntax. For now all that +matters is that `.` stands for any character and `*` stands for any amount, +even zero. In other words, we are looking for all entries which have an ID that +contains the word "ring" somewhere in it. This is a pretty dumb pattern because +it will also match words like "ringmail", but it's good enough for now. + +If you have typed the filter definition properly the text should change from +red to black and our table will be narrowed down a lot. Browse for an icon you +like and drag & drop its table row onto the *Icon* field of our new ring. + +That's it, you have now assigned a reference to an entry in another table to +the ring entry in the *Objects* table. Repeat the same process for the 3D +model, you can find the *Meshes* table under *Assets* → *Meshes*. + + +Adding the enchantment +====================== + +Putting everything you have learned so far to practice we can add the final and +most important part to our new ring: the enchantment. You know enough to +perform the following steps without guidance: Open the *Enchantments* table +(*Mechanics* → *Enchantments*) and create a new entry with the ID `Cats Eye`. +Edit it so that it has *Constant Effect* enchantment type. + +To add an effect to the enchantment right-click the *Magic Effects* table and +choose *Add new row*. You can edit the effects by right-clicking their table +cells. Set the effect to *NightEye*, range to *Self*, and both magnitudes to +`50`. The other properties are irrelevant. + +Once you are done add the new enchantment to our ring. That's it, we now have a +complete enchanted ring to play with. Let's take it for a test ride. + + +Playing your new addon +====================== + +Launch OpenMW and in the launcher under *Data Files* check your addon. Load a +game and open the console. We have only defined the ring, but we haven't placed +any instance of it anywhere in the game world, so we have to create one. In the +console type: + +.. code:: + + player->AddItem "ring_night_vision" 1 + +The part in quotation marks is the ID of our ring, you have to adjust it if you +chose a different ID. Exit the console and you should find a new ring in your +inventory. Equip it and you will instantly receive the *Night Vision* effect +for your character. + + +Conclusion +========== + +In this tutorial we have learned how to create a new addon, what tables are and +how to create new records. We have also taken a very brief glimpse at the +syntax of filters, a feature you will be using a lot when creating larger mods. + +This mod is a pure addition, it does not change any of the existing records. +However, if you want to actually present appealing content to the player rather +than just offering abstract definitions you will have to change the game's +content. In the next tutorial we will learn how to place the ring in the game +world so the player can find it legitimately. + + + +Adding the ring to the game's world +*********************************** + +Now that we have defined the ring it is time add it to the game world so the +player can find it legitimately. We will add the ring to a merchant, place it +in a chest and put it somewhere in plain sight. To this end we will have to +actually modify the contents of the game. + + +Subsection to come... +===================== + + + + diff --git a/extern/oics/CMakeLists.txt b/extern/oics/CMakeLists.txt index 52ffb42a26..00d77f52c6 100644 --- a/extern/oics/CMakeLists.txt +++ b/extern/oics/CMakeLists.txt @@ -1,32 +1,21 @@ -set(OICS_LIBRARY "oics") - -# Sources - -set(OICS_SOURCE_FILES - ICSChannel.cpp - ICSControl.cpp - ICSInputControlSystem.cpp - ICSInputControlSystem_keyboard.cpp - ICSInputControlSystem_mouse.cpp - ICSInputControlSystem_joystick.cpp - ICSPrerequisites.h -) - -set(TINYXML_SOURCE_FILES - tinyxml.cpp - tinyxmlparser.cpp - tinyxmlerror.cpp - tinystr.cpp +add_library(oics STATIC + ICSChannel.cpp + ICSControl.cpp + ICSInputControlSystem.cpp + ICSInputControlSystem_keyboard.cpp + ICSInputControlSystem_mouse.cpp + ICSInputControlSystem_joystick.cpp + ICSPrerequisites.h ) if(USE_SYSTEM_TINYXML) - add_library(${OICS_LIBRARY} STATIC ${OICS_SOURCE_FILES}) - target_link_libraries(${OICS_LIBRARY} ${TINYXML_LIBRARIES}) + target_link_libraries(oics ${TinyXML_LIBRARIES}) else() - add_library(${OICS_LIBRARY} STATIC - ${OICS_SOURCE_FILES} - ${TINYXML_SOURCE_FILES}) + add_library(local_tinyxml STATIC + tinyxml.cpp + tinyxmlparser.cpp + tinyxmlerror.cpp + tinystr.cpp + ) + target_link_libraries(oics local_tinyxml) endif() - -# Does this do anything? -link_directories(${CMAKE_CURRENT_BINARY_DIR}) diff --git a/extern/oics/tinyxml.h b/extern/oics/tinyxml.h index 50ad417fe5..7237825c5d 100644 --- a/extern/oics/tinyxml.h +++ b/extern/oics/tinyxml.h @@ -577,7 +577,7 @@ public: #endif /** Add a new node related to this. Adds a child past the LastChild. - Returns a pointer to the new object or NULL if an error occured. + Returns a pointer to the new object or NULL if an error occurred. */ TiXmlNode* InsertEndChild( const TiXmlNode& addThis ); @@ -594,17 +594,17 @@ public: TiXmlNode* LinkEndChild( TiXmlNode* addThis ); /** Add a new node related to this. Adds a child before the specified child. - Returns a pointer to the new object or NULL if an error occured. + Returns a pointer to the new object or NULL if an error occurred. */ TiXmlNode* InsertBeforeChild( TiXmlNode* beforeThis, const TiXmlNode& addThis ); /** Add a new node related to this. Adds a child after the specified child. - Returns a pointer to the new object or NULL if an error occured. + Returns a pointer to the new object or NULL if an error occurred. */ TiXmlNode* InsertAfterChild( TiXmlNode* afterThis, const TiXmlNode& addThis ); /** Replace a child of this node. - Returns a pointer to the new object or NULL if an error occured. + Returns a pointer to the new object or NULL if an error occurred. */ TiXmlNode* ReplaceChild( TiXmlNode* replaceThis, const TiXmlNode& withThis ); @@ -1471,7 +1471,7 @@ public: @sa SetTabSize, Row, Column */ int ErrorRow() const { return errorLocation.row+1; } - int ErrorCol() const { return errorLocation.col+1; } ///< The column where the error occured. See ErrorRow() + int ErrorCol() const { return errorLocation.col+1; } ///< The column where the error occurred. See ErrorRow() /** SetTabSize() allows the error reporting functions (ErrorRow() and ErrorCol()) to report the correct values for row and column. It does not change the output diff --git a/extern/osg-ffmpeg-videoplayer/CMakeLists.txt b/extern/osg-ffmpeg-videoplayer/CMakeLists.txt index 6009f69de5..5289cd3af9 100644 --- a/extern/osg-ffmpeg-videoplayer/CMakeLists.txt +++ b/extern/osg-ffmpeg-videoplayer/CMakeLists.txt @@ -10,8 +10,8 @@ set(OSG_FFMPEG_VIDEOPLAYER_SOURCE_FILES audiofactory.hpp ) -include_directories(${FFMPEG_INCLUDE_DIRS}) +include_directories(${FFmpeg_INCLUDE_DIRS}) add_library(${OSG_FFMPEG_VIDEOPLAYER_LIBRARY} STATIC ${OSG_FFMPEG_VIDEOPLAYER_SOURCE_FILES}) -target_link_libraries(${OSG_FFMPEG_VIDEOPLAYER_LIBRARY} ${FFMPEG_LIBRARIES} ${Boost_THREAD_LIBRARY}) +target_link_libraries(${OSG_FFMPEG_VIDEOPLAYER_LIBRARY} ${FFmpeg_LIBRARIES} ${Boost_THREAD_LIBRARY}) link_directories(${CMAKE_CURRENT_BINARY_DIR}) diff --git a/extern/osg-ffmpeg-videoplayer/videostate.cpp b/extern/osg-ffmpeg-videoplayer/videostate.cpp index 3c9279ea25..5849e87187 100644 --- a/extern/osg-ffmpeg-videoplayer/videostate.cpp +++ b/extern/osg-ffmpeg-videoplayer/videostate.cpp @@ -528,7 +528,7 @@ void VideoState::decode_thread_loop(VideoState *self) } } catch(std::exception& e) { - std::cerr << "An error occured playing the video: " << e.what () << std::endl; + std::cerr << "An error occurred playing the video: " << e.what () << std::endl; } self->mQuit = true; diff --git a/extern/osgQt/CMakeLists.txt b/extern/osgQt/CMakeLists.txt new file mode 100644 index 0000000000..3bd08a390a --- /dev/null +++ b/extern/osgQt/CMakeLists.txt @@ -0,0 +1,20 @@ +set(OSGQT_LIBRARY "osgQt") + +# Sources + +set(OSGQT_SOURCE_FILES + GraphicsWindowQt.cpp +) + +add_library(${OSGQT_LIBRARY} STATIC ${OSGQT_SOURCE_FILES}) + +if (DESIRED_QT_VERSION MATCHES 4) + include(${QT_USE_FILE}) + target_link_libraries(${OSGQT_LIBRARY} ${QT_QTCORE_LIBRARY} ${QT_QTOPENGL_LIBRARY}) +else() + qt5_use_modules(${OSGQT_LIBRARY} Core OpenGL) +endif() + +link_directories(${CMAKE_CURRENT_BINARY_DIR}) + +set(EXTERN_OSGQT_LIBRARY ${OSGQT_LIBRARY} PARENT_SCOPE) diff --git a/extern/osgQt/GraphicsWindowQt b/extern/osgQt/GraphicsWindowQt new file mode 100644 index 0000000000..3be1da0515 --- /dev/null +++ b/extern/osgQt/GraphicsWindowQt @@ -0,0 +1,167 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 2009 Wang Rui + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenSceneGraph Public License for more details. +*/ + +#ifndef OSGVIEWER_GRAPHICSWINDOWQT +#define OSGVIEWER_GRAPHICSWINDOWQT + +#include + +#include + +#include +#include +#include +#include +#include + +class QInputEvent; +class QGestureEvent; + +namespace osgViewer { + class ViewerBase; +} + +namespace osgQt +{ + +// forward declarations +class GraphicsWindowQt; + +/** The function sets the viewer that will be used after entering + * the Qt main loop (QCoreApplication::exec()). + * + * The function also initializes internal structures required for proper + * scene rendering. + * + * The method must be called from main thread. */ +void setViewer( osgViewer::ViewerBase *viewer ); + + +class GLWidget : public QGLWidget +{ + typedef QGLWidget inherited; + +public: + + GLWidget( QWidget* parent = NULL, const QGLWidget* shareWidget = NULL, Qt::WindowFlags f = 0); + GLWidget( QGLContext* context, QWidget* parent = NULL, const QGLWidget* shareWidget = NULL, Qt::WindowFlags f = 0); + GLWidget( const QGLFormat& format, QWidget* parent = NULL, const QGLWidget* shareWidget = NULL, Qt::WindowFlags f = 0); + virtual ~GLWidget(); + + inline void setGraphicsWindow( GraphicsWindowQt* gw ) { _gw = gw; } + inline GraphicsWindowQt* getGraphicsWindow() { return _gw; } + inline const GraphicsWindowQt* getGraphicsWindow() const { return _gw; } + +protected: + + int getNumDeferredEvents() + { + QMutexLocker lock(&_deferredEventQueueMutex); + return _deferredEventQueue.count(); + } + void enqueueDeferredEvent(QEvent::Type eventType, QEvent::Type removeEventType = QEvent::None) + { + QMutexLocker lock(&_deferredEventQueueMutex); + + if (removeEventType != QEvent::None) + { + if (_deferredEventQueue.removeOne(removeEventType)) + _eventCompressor.remove(eventType); + } + + if (_eventCompressor.find(eventType) == _eventCompressor.end()) + { + _deferredEventQueue.enqueue(eventType); + _eventCompressor.insert(eventType); + } + } + void processDeferredEvents(); + + friend class GraphicsWindowQt; + GraphicsWindowQt* _gw; + + QMutex _deferredEventQueueMutex; + QQueue _deferredEventQueue; + QSet _eventCompressor; + + qreal _devicePixelRatio; + + virtual void resizeEvent( QResizeEvent* event ); + virtual void moveEvent( QMoveEvent* event ); + virtual void glDraw(); + virtual bool event( QEvent* event ); +}; + +class GraphicsWindowQt : public osgViewer::GraphicsWindow +{ +public: + GraphicsWindowQt( osg::GraphicsContext::Traits* traits, QWidget* parent = NULL, const QGLWidget* shareWidget = NULL, Qt::WindowFlags f = 0 ); + GraphicsWindowQt( GLWidget* widget ); + virtual ~GraphicsWindowQt(); + + inline GLWidget* getGLWidget() { return _widget; } + inline const GLWidget* getGLWidget() const { return _widget; } + + /// deprecated + inline GLWidget* getGraphWidget() { return _widget; } + /// deprecated + inline const GLWidget* getGraphWidget() const { return _widget; } + + struct WindowData : public osg::Referenced + { + WindowData( GLWidget* widget = NULL, QWidget* parent = NULL ): _widget(widget), _parent(parent) {} + GLWidget* _widget; + QWidget* _parent; + }; + + bool init( QWidget* parent, const QGLWidget* shareWidget, Qt::WindowFlags f ); + + static QGLFormat traits2qglFormat( const osg::GraphicsContext::Traits* traits ); + static void qglFormat2traits( const QGLFormat& format, osg::GraphicsContext::Traits* traits ); + static osg::GraphicsContext::Traits* createTraits( const QGLWidget* widget ); + + virtual bool setWindowRectangleImplementation( int x, int y, int width, int height ); + virtual void getWindowRectangle( int& x, int& y, int& width, int& height ); + virtual bool setWindowDecorationImplementation( bool windowDecoration ); + virtual bool getWindowDecoration() const; + virtual void grabFocus(); + virtual void grabFocusIfPointerInWindow(); + virtual void raiseWindow(); + virtual void setWindowName( const std::string& name ); + virtual std::string getWindowName(); + virtual void useCursor( bool cursorOn ); + virtual void setCursor( MouseCursor cursor ); + + virtual bool valid() const; + virtual bool realizeImplementation(); + virtual bool isRealizedImplementation() const; + virtual void closeImplementation(); + virtual bool makeCurrentImplementation(); + virtual bool releaseContextImplementation(); + virtual void swapBuffersImplementation(); + virtual void runOperations(); + + virtual void requestWarpPointer( float x, float y ); + +protected: + + friend class GLWidget; + GLWidget* _widget; + bool _ownsWidget; + QCursor _currentCursor; + bool _realized; +}; + +} + +#endif diff --git a/extern/osgQt/GraphicsWindowQt.cpp b/extern/osgQt/GraphicsWindowQt.cpp new file mode 100644 index 0000000000..17150bed4b --- /dev/null +++ b/extern/osgQt/GraphicsWindowQt.cpp @@ -0,0 +1,740 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 2009 Wang Rui + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenSceneGraph Public License for more details. +*/ + +#include "GraphicsWindowQt" + +#include +#include +#include +#include + +#if (QT_VERSION>=QT_VERSION_CHECK(4, 6, 0)) +# define USE_GESTURES +# include +# include +#endif + +using namespace osgQt; + +/// The object responsible for the scene re-rendering. +class HeartBeat : public QObject { +public: + int _timerId; + osg::Timer _lastFrameStartTime; + osg::observer_ptr< osgViewer::ViewerBase > _viewer; + + virtual ~HeartBeat(); + + void init( osgViewer::ViewerBase *viewer ); + void stopTimer(); + void timerEvent( QTimerEvent *event ); + + static HeartBeat* instance(); +private: + HeartBeat(); + + static QPointer heartBeat; +}; + +QPointer HeartBeat::heartBeat; + +#if (QT_VERSION < QT_VERSION_CHECK(5, 2, 0)) + #define GETDEVICEPIXELRATIO() 1.0 +#else + #define GETDEVICEPIXELRATIO() devicePixelRatio() +#endif + +GLWidget::GLWidget( QWidget* parent, const QGLWidget* shareWidget, Qt::WindowFlags f) +: QGLWidget(parent, shareWidget, f), _gw( NULL ) +{ + _devicePixelRatio = GETDEVICEPIXELRATIO(); +} + +GLWidget::GLWidget( QGLContext* context, QWidget* parent, const QGLWidget* shareWidget, Qt::WindowFlags f) +: QGLWidget(context, parent, shareWidget, f), _gw( NULL ) +{ + _devicePixelRatio = GETDEVICEPIXELRATIO(); +} + +GLWidget::GLWidget( const QGLFormat& format, QWidget* parent, const QGLWidget* shareWidget, Qt::WindowFlags f) +: QGLWidget(format, parent, shareWidget, f), _gw( NULL ) +{ + _devicePixelRatio = GETDEVICEPIXELRATIO(); +} + +GLWidget::~GLWidget() +{ + // close GraphicsWindowQt and remove the reference to us + if( _gw ) + { + _gw->close(); + _gw->_widget = NULL; + _gw = NULL; + } +} + +void GLWidget::processDeferredEvents() +{ + QQueue deferredEventQueueCopy; + { + QMutexLocker lock(&_deferredEventQueueMutex); + deferredEventQueueCopy = _deferredEventQueue; + _eventCompressor.clear(); + _deferredEventQueue.clear(); + } + + while (!deferredEventQueueCopy.isEmpty()) + { + QEvent event(deferredEventQueueCopy.dequeue()); + QGLWidget::event(&event); + } +} + +bool GLWidget::event( QEvent* event ) +{ + + // QEvent::Hide + // + // workaround "Qt-workaround" that does glFinish before hiding the widget + // (the Qt workaround was seen at least in Qt 4.6.3 and 4.7.0) + // + // Qt makes the context current, performs glFinish, and releases the context. + // This makes the problem in OSG multithreaded environment as the context + // is active in another thread, thus it can not be made current for the purpose + // of glFinish in this thread. + + // QEvent::ParentChange + // + // Reparenting GLWidget may create a new underlying window and a new GL context. + // Qt will then call doneCurrent on the GL context about to be deleted. The thread + // where old GL context was current has no longer current context to render to and + // we cannot make new GL context current in this thread. + + // We workaround above problems by deferring execution of problematic event requests. + // These events has to be enqueue and executed later in a main GUI thread (GUI operations + // outside the main thread are not allowed) just before makeCurrent is called from the + // right thread. The good place for doing that is right after swap in a swapBuffersImplementation. + + if (event->type() == QEvent::Hide) + { + // enqueue only the last of QEvent::Hide and QEvent::Show + enqueueDeferredEvent(QEvent::Hide, QEvent::Show); + return true; + } + else if (event->type() == QEvent::Show) + { + // enqueue only the last of QEvent::Show or QEvent::Hide + enqueueDeferredEvent(QEvent::Show, QEvent::Hide); + return true; + } + else if (event->type() == QEvent::ParentChange) + { + // enqueue only the last QEvent::ParentChange + enqueueDeferredEvent(QEvent::ParentChange); + return true; + } + + // perform regular event handling + return QGLWidget::event( event ); +} + +void GLWidget::resizeEvent( QResizeEvent* event ) +{ + const QSize& size = event->size(); + + int scaled_width = static_cast(size.width()*_devicePixelRatio); + int scaled_height = static_cast(size.height()*_devicePixelRatio); + _gw->resized( x(), y(), scaled_width, scaled_height); + _gw->getEventQueue()->windowResize( x(), y(), scaled_width, scaled_height ); + _gw->requestRedraw(); +} + +void GLWidget::moveEvent( QMoveEvent* event ) +{ + const QPoint& pos = event->pos(); + int scaled_width = static_cast(width()*_devicePixelRatio); + int scaled_height = static_cast(height()*_devicePixelRatio); + _gw->resized( pos.x(), pos.y(), scaled_width, scaled_height ); + _gw->getEventQueue()->windowResize( pos.x(), pos.y(), scaled_width, scaled_height ); +} + +void GLWidget::glDraw() +{ + _gw->requestRedraw(); +} + +GraphicsWindowQt::GraphicsWindowQt( osg::GraphicsContext::Traits* traits, QWidget* parent, const QGLWidget* shareWidget, Qt::WindowFlags f ) +: _realized(false) +{ + + _widget = NULL; + _traits = traits; + init( parent, shareWidget, f ); +} + +GraphicsWindowQt::GraphicsWindowQt( GLWidget* widget ) +: _realized(false) +{ + _widget = widget; + _traits = _widget ? createTraits( _widget ) : new osg::GraphicsContext::Traits; + init( NULL, NULL, 0 ); +} + +GraphicsWindowQt::~GraphicsWindowQt() +{ + close(); + + // remove reference from GLWidget + if ( _widget ) + _widget->_gw = NULL; +} + +bool GraphicsWindowQt::init( QWidget* parent, const QGLWidget* shareWidget, Qt::WindowFlags f ) +{ + // update _widget and parent by WindowData + WindowData* windowData = _traits.get() ? dynamic_cast(_traits->inheritedWindowData.get()) : 0; + if ( !_widget ) + _widget = windowData ? windowData->_widget : NULL; + if ( !parent ) + parent = windowData ? windowData->_parent : NULL; + + // create widget if it does not exist + _ownsWidget = _widget == NULL; + if ( !_widget ) + { + // shareWidget + if ( !shareWidget ) { + GraphicsWindowQt* sharedContextQt = dynamic_cast(_traits->sharedContext.get()); + if ( sharedContextQt ) + shareWidget = sharedContextQt->getGLWidget(); + } + + // WindowFlags + Qt::WindowFlags flags = f | Qt::Window | Qt::CustomizeWindowHint; + if ( _traits->windowDecoration ) + flags |= Qt::WindowTitleHint | Qt::WindowMinMaxButtonsHint | Qt::WindowSystemMenuHint +#if (QT_VERSION_CHECK(4, 5, 0) <= QT_VERSION) + | Qt::WindowCloseButtonHint +#endif + ; + + // create widget + _widget = new GLWidget( traits2qglFormat( _traits.get() ), parent, shareWidget, flags ); + } + + // set widget name and position + // (do not set it when we inherited the widget) + if ( _ownsWidget ) + { + _widget->setWindowTitle( _traits->windowName.c_str() ); + _widget->move( _traits->x, _traits->y ); + if ( !_traits->supportsResize ) _widget->setFixedSize( _traits->width, _traits->height ); + else _widget->resize( _traits->width, _traits->height ); + } + + // initialize widget properties + _widget->setAutoBufferSwap( false ); + _widget->setMouseTracking( true ); + _widget->setGraphicsWindow( this ); + useCursor( _traits->useCursor ); + + // initialize State + setState( new osg::State ); + getState()->setGraphicsContext(this); + + // initialize contextID + if ( _traits.valid() && _traits->sharedContext.valid() ) + { + getState()->setContextID( _traits->sharedContext->getState()->getContextID() ); + incrementContextIDUsageCount( getState()->getContextID() ); + } + else + { + getState()->setContextID( osg::GraphicsContext::createNewContextID() ); + } + + // make sure the event queue has the correct window rectangle size and input range + getEventQueue()->syncWindowRectangleWithGraphicsContext(); + + return true; +} + +QGLFormat GraphicsWindowQt::traits2qglFormat( const osg::GraphicsContext::Traits* traits ) +{ + QGLFormat format( QGLFormat::defaultFormat() ); + + format.setAlphaBufferSize( traits->alpha ); + format.setRedBufferSize( traits->red ); + format.setGreenBufferSize( traits->green ); + format.setBlueBufferSize( traits->blue ); + format.setDepthBufferSize( traits->depth ); + format.setStencilBufferSize( traits->stencil ); + format.setSampleBuffers( traits->sampleBuffers ); + format.setSamples( traits->samples ); + + format.setAlpha( traits->alpha>0 ); + format.setDepth( traits->depth>0 ); + format.setStencil( traits->stencil>0 ); + format.setDoubleBuffer( traits->doubleBuffer ); + format.setSwapInterval( traits->vsync ? 1 : 0 ); + format.setStereo( traits->quadBufferStereo ? 1 : 0 ); + + return format; +} + +void GraphicsWindowQt::qglFormat2traits( const QGLFormat& format, osg::GraphicsContext::Traits* traits ) +{ + traits->red = format.redBufferSize(); + traits->green = format.greenBufferSize(); + traits->blue = format.blueBufferSize(); + traits->alpha = format.alpha() ? format.alphaBufferSize() : 0; + traits->depth = format.depth() ? format.depthBufferSize() : 0; + traits->stencil = format.stencil() ? format.stencilBufferSize() : 0; + + traits->sampleBuffers = format.sampleBuffers() ? 1 : 0; + traits->samples = format.samples(); + + traits->quadBufferStereo = format.stereo(); + traits->doubleBuffer = format.doubleBuffer(); + + traits->vsync = format.swapInterval() >= 1; +} + +osg::GraphicsContext::Traits* GraphicsWindowQt::createTraits( const QGLWidget* widget ) +{ + osg::GraphicsContext::Traits *traits = new osg::GraphicsContext::Traits; + + qglFormat2traits( widget->format(), traits ); + + QRect r = widget->geometry(); + traits->x = r.x(); + traits->y = r.y(); + traits->width = r.width(); + traits->height = r.height(); + + traits->windowName = widget->windowTitle().toLocal8Bit().data(); + Qt::WindowFlags f = widget->windowFlags(); + traits->windowDecoration = ( f & Qt::WindowTitleHint ) && + ( f & Qt::WindowMinMaxButtonsHint ) && + ( f & Qt::WindowSystemMenuHint ); + QSizePolicy sp = widget->sizePolicy(); + traits->supportsResize = sp.horizontalPolicy() != QSizePolicy::Fixed || + sp.verticalPolicy() != QSizePolicy::Fixed; + + return traits; +} + +bool GraphicsWindowQt::setWindowRectangleImplementation( int x, int y, int width, int height ) +{ + if ( _widget == NULL ) + return false; + + _widget->setGeometry( x, y, width, height ); + return true; +} + +void GraphicsWindowQt::getWindowRectangle( int& x, int& y, int& width, int& height ) +{ + if ( _widget ) + { + const QRect& geom = _widget->geometry(); + x = geom.x(); + y = geom.y(); + width = geom.width(); + height = geom.height(); + } +} + +bool GraphicsWindowQt::setWindowDecorationImplementation( bool windowDecoration ) +{ + Qt::WindowFlags flags = Qt::Window|Qt::CustomizeWindowHint;//|Qt::WindowStaysOnTopHint; + if ( windowDecoration ) + flags |= Qt::WindowTitleHint|Qt::WindowMinMaxButtonsHint|Qt::WindowSystemMenuHint; + _traits->windowDecoration = windowDecoration; + + if ( _widget ) + { + _widget->setWindowFlags( flags ); + + return true; + } + + return false; +} + +bool GraphicsWindowQt::getWindowDecoration() const +{ + return _traits->windowDecoration; +} + +void GraphicsWindowQt::grabFocus() +{ + if ( _widget ) + _widget->setFocus( Qt::ActiveWindowFocusReason ); +} + +void GraphicsWindowQt::grabFocusIfPointerInWindow() +{ + if ( _widget->underMouse() ) + _widget->setFocus( Qt::ActiveWindowFocusReason ); +} + +void GraphicsWindowQt::raiseWindow() +{ + if ( _widget ) + _widget->raise(); +} + +void GraphicsWindowQt::setWindowName( const std::string& name ) +{ + if ( _widget ) + _widget->setWindowTitle( name.c_str() ); +} + +std::string GraphicsWindowQt::getWindowName() +{ + return _widget ? _widget->windowTitle().toStdString() : ""; +} + +void GraphicsWindowQt::useCursor( bool cursorOn ) +{ + if ( _widget ) + { + _traits->useCursor = cursorOn; + if ( !cursorOn ) _widget->setCursor( Qt::BlankCursor ); + else _widget->setCursor( _currentCursor ); + } +} + +void GraphicsWindowQt::setCursor( MouseCursor cursor ) +{ + if ( cursor==InheritCursor && _widget ) + { + _widget->unsetCursor(); + } + + switch ( cursor ) + { + case NoCursor: _currentCursor = Qt::BlankCursor; break; + case RightArrowCursor: case LeftArrowCursor: _currentCursor = Qt::ArrowCursor; break; + case InfoCursor: _currentCursor = Qt::SizeAllCursor; break; + case DestroyCursor: _currentCursor = Qt::ForbiddenCursor; break; + case HelpCursor: _currentCursor = Qt::WhatsThisCursor; break; + case CycleCursor: _currentCursor = Qt::ForbiddenCursor; break; + case SprayCursor: _currentCursor = Qt::SizeAllCursor; break; + case WaitCursor: _currentCursor = Qt::WaitCursor; break; + case TextCursor: _currentCursor = Qt::IBeamCursor; break; + case CrosshairCursor: _currentCursor = Qt::CrossCursor; break; + case HandCursor: _currentCursor = Qt::OpenHandCursor; break; + case UpDownCursor: _currentCursor = Qt::SizeVerCursor; break; + case LeftRightCursor: _currentCursor = Qt::SizeHorCursor; break; + case TopSideCursor: case BottomSideCursor: _currentCursor = Qt::UpArrowCursor; break; + case LeftSideCursor: case RightSideCursor: _currentCursor = Qt::SizeHorCursor; break; + case TopLeftCorner: _currentCursor = Qt::SizeBDiagCursor; break; + case TopRightCorner: _currentCursor = Qt::SizeFDiagCursor; break; + case BottomRightCorner: _currentCursor = Qt::SizeBDiagCursor; break; + case BottomLeftCorner: _currentCursor = Qt::SizeFDiagCursor; break; + default: break; + }; + if ( _widget ) _widget->setCursor( _currentCursor ); +} + +bool GraphicsWindowQt::valid() const +{ + return _widget && _widget->isValid(); +} + +bool GraphicsWindowQt::realizeImplementation() +{ + // save the current context + // note: this will save only Qt-based contexts + const QGLContext *savedContext = QGLContext::currentContext(); + + // initialize GL context for the widget + if ( !valid() ) + _widget->glInit(); + + // make current + _realized = true; + bool result = makeCurrent(); + _realized = false; + + // fail if we do not have current context + if ( !result ) + { + if ( savedContext ) + const_cast< QGLContext* >( savedContext )->makeCurrent(); + + OSG_WARN << "Window realize: Can make context current." << std::endl; + return false; + } + + _realized = true; + + // make sure the event queue has the correct window rectangle size and input range + getEventQueue()->syncWindowRectangleWithGraphicsContext(); + + // make this window's context not current + // note: this must be done as we will probably make the context current from another thread + // and it is not allowed to have one context current in two threads + if( !releaseContext() ) + OSG_WARN << "Window realize: Can not release context." << std::endl; + + // restore previous context + if ( savedContext ) + const_cast< QGLContext* >( savedContext )->makeCurrent(); + + return true; +} + +bool GraphicsWindowQt::isRealizedImplementation() const +{ + return _realized; +} + +void GraphicsWindowQt::closeImplementation() +{ + if ( _widget ) + _widget->close(); + _realized = false; +} + +void GraphicsWindowQt::runOperations() +{ + // While in graphics thread this is last chance to do something useful before + // graphics thread will execute its operations. + if (_widget->getNumDeferredEvents() > 0) + _widget->processDeferredEvents(); + + if (QGLContext::currentContext() != _widget->context()) + _widget->makeCurrent(); + + GraphicsWindow::runOperations(); +} + +bool GraphicsWindowQt::makeCurrentImplementation() +{ + if (_widget->getNumDeferredEvents() > 0) + _widget->processDeferredEvents(); + + _widget->makeCurrent(); + + return true; +} + +bool GraphicsWindowQt::releaseContextImplementation() +{ + _widget->doneCurrent(); + return true; +} + +void GraphicsWindowQt::swapBuffersImplementation() +{ + _widget->swapBuffers(); + + // FIXME: the processDeferredEvents should really be executed in a GUI (main) thread context but + // I couln't find any reliable way to do this. For now, lets hope non of *GUI thread only operations* will + // be executed in a QGLWidget::event handler. On the other hand, calling GUI only operations in the + // QGLWidget event handler is an indication of a Qt bug. + if (_widget->getNumDeferredEvents() > 0) + _widget->processDeferredEvents(); + + // We need to call makeCurrent here to restore our previously current context + // which may be changed by the processDeferredEvents function. + if (QGLContext::currentContext() != _widget->context()) + _widget->makeCurrent(); +} + +void GraphicsWindowQt::requestWarpPointer( float x, float y ) +{ + if ( _widget ) + QCursor::setPos( _widget->mapToGlobal(QPoint((int)x,(int)y)) ); +} + + +class QtWindowingSystem : public osg::GraphicsContext::WindowingSystemInterface +{ +public: + + QtWindowingSystem() + { + OSG_INFO << "QtWindowingSystemInterface()" << std::endl; + } + + ~QtWindowingSystem() + { + if (osg::Referenced::getDeleteHandler()) + { + osg::Referenced::getDeleteHandler()->setNumFramesToRetainObjects(0); + osg::Referenced::getDeleteHandler()->flushAll(); + } + } + + // Access the Qt windowing system through this singleton class. + static QtWindowingSystem* getInterface() + { + static QtWindowingSystem* qtInterface = new QtWindowingSystem; + return qtInterface; + } + + // Return the number of screens present in the system + virtual unsigned int getNumScreens( const osg::GraphicsContext::ScreenIdentifier& /*si*/ ) + { + OSG_WARN << "osgQt: getNumScreens() not implemented yet." << std::endl; + return 0; + } + + // Return the resolution of specified screen + // (0,0) is returned if screen is unknown + virtual void getScreenSettings( const osg::GraphicsContext::ScreenIdentifier& /*si*/, osg::GraphicsContext::ScreenSettings & /*resolution*/ ) + { + OSG_WARN << "osgQt: getScreenSettings() not implemented yet." << std::endl; + } + + // Set the resolution for given screen + virtual bool setScreenSettings( const osg::GraphicsContext::ScreenIdentifier& /*si*/, const osg::GraphicsContext::ScreenSettings & /*resolution*/ ) + { + OSG_WARN << "osgQt: setScreenSettings() not implemented yet." << std::endl; + return false; + } + + // Enumerates available resolutions + virtual void enumerateScreenSettings( const osg::GraphicsContext::ScreenIdentifier& /*screenIdentifier*/, osg::GraphicsContext::ScreenSettingsList & /*resolution*/ ) + { + OSG_WARN << "osgQt: enumerateScreenSettings() not implemented yet." << std::endl; + } + + // Create a graphics context with given traits + virtual osg::GraphicsContext* createGraphicsContext( osg::GraphicsContext::Traits* traits ) + { + if (traits->pbuffer) + { + OSG_WARN << "osgQt: createGraphicsContext - pbuffer not implemented yet." << std::endl; + return NULL; + } + else + { + osg::ref_ptr< GraphicsWindowQt > window = new GraphicsWindowQt( traits ); + if (window->valid()) return window.release(); + else return NULL; + } + } + +private: + + // No implementation for these + QtWindowingSystem( const QtWindowingSystem& ); + QtWindowingSystem& operator=( const QtWindowingSystem& ); +}; + + +void osgQt::setViewer( osgViewer::ViewerBase *viewer ) +{ + HeartBeat::instance()->init( viewer ); +} + + +/// Constructor. Must be called from main thread. +HeartBeat::HeartBeat() : _timerId( 0 ) +{ +} + + +/// Destructor. Must be called from main thread. +HeartBeat::~HeartBeat() +{ + stopTimer(); +} + +HeartBeat* HeartBeat::instance() +{ + if (!heartBeat) + { + heartBeat = new HeartBeat(); + } + return heartBeat; +} + +void HeartBeat::stopTimer() +{ + if ( _timerId != 0 ) + { + killTimer( _timerId ); + _timerId = 0; + } +} + + +/// Initializes the loop for viewer. Must be called from main thread. +void HeartBeat::init( osgViewer::ViewerBase *viewer ) +{ + if( _viewer == viewer ) + return; + + stopTimer(); + + _viewer = viewer; + + if( viewer ) + { + _timerId = startTimer( 0 ); + _lastFrameStartTime.setStartTick( 0 ); + } +} + + +void HeartBeat::timerEvent( QTimerEvent * /*event*/ ) +{ + osg::ref_ptr< osgViewer::ViewerBase > viewer; + if( !_viewer.lock( viewer ) ) + { + // viewer has been deleted -> stop timer + stopTimer(); + return; + } + + // limit the frame rate + if( viewer->getRunMaxFrameRate() > 0.0) + { + double dt = _lastFrameStartTime.time_s(); + double minFrameTime = 1.0 / viewer->getRunMaxFrameRate(); + if (dt < minFrameTime) + OpenThreads::Thread::microSleep(static_cast(1000000.0*(minFrameTime-dt))); + } + else + { + // avoid excessive CPU loading when no frame is required in ON_DEMAND mode + if( viewer->getRunFrameScheme() == osgViewer::ViewerBase::ON_DEMAND ) + { + double dt = _lastFrameStartTime.time_s(); + if (dt < 0.01) + OpenThreads::Thread::microSleep(static_cast(1000000.0*(0.01-dt))); + } + + // record start frame time + _lastFrameStartTime.setStartTick(); + + // make frame + if( viewer->getRunFrameScheme() == osgViewer::ViewerBase::ON_DEMAND ) + { + if( viewer->checkNeedToDoFrame() ) + { + viewer->frame(); + } + } + else + { + viewer->frame(); + } + } +} diff --git a/files/mygui/openmw_pointer.xml b/files/mygui/openmw_pointer.xml index a55a5453c0..4a2f818600 100644 --- a/files/mygui/openmw_pointer.xml +++ b/files/mygui/openmw_pointer.xml @@ -23,13 +23,13 @@ - + - + diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index 6d2424aa5b..8ff850cae5 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -10,7 +10,7 @@ - + @@ -31,7 +31,7 @@ - + @@ -52,7 +52,7 @@ - + @@ -61,6 +61,8 @@ + + @@ -72,7 +74,7 @@ - + @@ -82,7 +84,7 @@ - + @@ -92,7 +94,7 @@ - + @@ -102,7 +104,7 @@ - + @@ -117,7 +119,7 @@ - + @@ -128,7 +130,7 @@ - + @@ -139,7 +141,7 @@ - + @@ -150,7 +152,7 @@ - + @@ -161,7 +163,7 @@ - + @@ -187,7 +189,7 @@ - + @@ -199,7 +201,7 @@ - + @@ -255,18 +257,6 @@ - - - - - - - - - - - - @@ -274,9 +264,8 @@ - - + @@ -285,6 +274,8 @@ + + @@ -329,17 +320,19 @@ - + + + - + @@ -361,7 +354,7 @@ - + @@ -372,7 +365,7 @@ - + @@ -391,12 +384,21 @@ + + + + + + + + + + @@ -474,6 +476,7 @@ + --> diff --git a/files/mygui/openmw_windows.skin.xml b/files/mygui/openmw_windows.skin.xml index 682d89ebcc..22270bb9f6 100644 --- a/files/mygui/openmw_windows.skin.xml +++ b/files/mygui/openmw_windows.skin.xml @@ -11,41 +11,49 @@ + + + + + + + + @@ -53,41 +61,49 @@ + + + + + + + + diff --git a/files/opencs/raster/scene-exterior-parts/0_backdrop.png b/files/opencs/raster/scene-exterior-parts/0_backdrop.png new file mode 100644 index 0000000000..f95983cc15 Binary files /dev/null and b/files/opencs/raster/scene-exterior-parts/0_backdrop.png differ diff --git a/files/opencs/raster/scene-exterior-parts/1_grid.png b/files/opencs/raster/scene-exterior-parts/1_grid.png new file mode 100644 index 0000000000..9711df62ca Binary files /dev/null and b/files/opencs/raster/scene-exterior-parts/1_grid.png differ diff --git a/files/opencs/raster/scene-exterior-parts/2_arrows.png b/files/opencs/raster/scene-exterior-parts/2_arrows.png new file mode 100644 index 0000000000..f388255ade Binary files /dev/null and b/files/opencs/raster/scene-exterior-parts/2_arrows.png differ diff --git a/files/opencs/raster/scene-exterior-parts/3_cell_marker.png b/files/opencs/raster/scene-exterior-parts/3_cell_marker.png new file mode 100644 index 0000000000..a2de374b70 Binary files /dev/null and b/files/opencs/raster/scene-exterior-parts/3_cell_marker.png differ diff --git a/files/opencs/raster/scene-exterior-parts/4_terrain.png b/files/opencs/raster/scene-exterior-parts/4_terrain.png new file mode 100644 index 0000000000..95901c805b Binary files /dev/null and b/files/opencs/raster/scene-exterior-parts/4_terrain.png differ diff --git a/files/opencs/raster/scene-exterior-parts/5_divider.png b/files/opencs/raster/scene-exterior-parts/5_divider.png new file mode 100644 index 0000000000..b34af53c5a Binary files /dev/null and b/files/opencs/raster/scene-exterior-parts/5_divider.png differ diff --git a/files/opencs/raster/scene-exterior-parts/composite_the_icons.sh b/files/opencs/raster/scene-exterior-parts/composite_the_icons.sh new file mode 100755 index 0000000000..939bda923f --- /dev/null +++ b/files/opencs/raster/scene-exterior-parts/composite_the_icons.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# Run this from the "parts" directory to composite the final 32 icons +# Also creates status (masked) variants of the icons while at it + +set -e + +mkdir -p status +i=0 + +for grid in '' '*_grid*'; do +for arrows in '' '*_arrows*'; do +for cell_marker in '' '*_cell_marker*'; do + files=$(echo $grid $arrows $cell_marker | tr ' ' '\n' | sort -n | tr '\n' ' ') + convert *backdrop* $files *terrain* -background transparent -mosaic \ + -crop '23x48+25+0' *mask* -mosaic status/$i.png + i=$((i+1)) +done;done;done; diff --git a/files/opencs/raster/scene-exterior-parts/mask.png b/files/opencs/raster/scene-exterior-parts/mask.png new file mode 100644 index 0000000000..8bd6f63624 Binary files /dev/null and b/files/opencs/raster/scene-exterior-parts/mask.png differ diff --git a/files/opencs/raster/scene-view-parts/mask.png b/files/opencs/raster/scene-view-parts/mask.png index e075fe8f5b..65c6d736ba 100644 Binary files a/files/opencs/raster/scene-view-parts/mask.png and b/files/opencs/raster/scene-view-parts/mask.png differ diff --git a/files/opencs/resources.qrc b/files/opencs/resources.qrc index 1bcd3ab4f2..e9fb12cbe5 100644 --- a/files/opencs/resources.qrc +++ b/files/opencs/resources.qrc @@ -117,5 +117,16 @@ scene-view-status-29.png scene-view-status-30.png scene-view-status-31.png + scene-exterior-arrows.png + scene-exterior-borders.png + scene-exterior-marker.png + scene-exterior-status-0.png + scene-exterior-status-1.png + scene-exterior-status-2.png + scene-exterior-status-3.png + scene-exterior-status-4.png + scene-exterior-status-5.png + scene-exterior-status-6.png + scene-exterior-status-7.png diff --git a/files/opencs/scalable/scene-exterior.svg b/files/opencs/scalable/scene-exterior.svg new file mode 100644 index 0000000000..7021a04404 --- /dev/null +++ b/files/opencs/scalable/scene-exterior.svg @@ -0,0 +1,599 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + Sergey "Shnatsel" Davidoff <shnatsel@gmail.com> + + + + + Sergey "Shnatsel" Davidoff <shnatsel@gmail.com> + + + + + + + + + + + + This icon was designed bySergey "Shnatsel" Davidofffor OpenMW content editorLicensed under GNU GPLv3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-exterior-arrows.png b/files/opencs/scene-exterior-arrows.png new file mode 100644 index 0000000000..f388255ade Binary files /dev/null and b/files/opencs/scene-exterior-arrows.png differ diff --git a/files/opencs/scene-exterior-borders.png b/files/opencs/scene-exterior-borders.png new file mode 100644 index 0000000000..9711df62ca Binary files /dev/null and b/files/opencs/scene-exterior-borders.png differ diff --git a/files/opencs/scene-exterior-marker.png b/files/opencs/scene-exterior-marker.png new file mode 100644 index 0000000000..a2de374b70 Binary files /dev/null and b/files/opencs/scene-exterior-marker.png differ diff --git a/files/opencs/scene-exterior-markers.png b/files/opencs/scene-exterior-markers.png new file mode 100644 index 0000000000..a2de374b70 Binary files /dev/null and b/files/opencs/scene-exterior-markers.png differ diff --git a/files/opencs/scene-exterior-status-0.png b/files/opencs/scene-exterior-status-0.png new file mode 100644 index 0000000000..f95de53e3c Binary files /dev/null and b/files/opencs/scene-exterior-status-0.png differ diff --git a/files/opencs/scene-exterior-status-1.png b/files/opencs/scene-exterior-status-1.png new file mode 100644 index 0000000000..09427b0c2a Binary files /dev/null and b/files/opencs/scene-exterior-status-1.png differ diff --git a/files/opencs/scene-exterior-status-2.png b/files/opencs/scene-exterior-status-2.png new file mode 100644 index 0000000000..086fe7b0aa Binary files /dev/null and b/files/opencs/scene-exterior-status-2.png differ diff --git a/files/opencs/scene-exterior-status-3.png b/files/opencs/scene-exterior-status-3.png new file mode 100644 index 0000000000..68cdb07d24 Binary files /dev/null and b/files/opencs/scene-exterior-status-3.png differ diff --git a/files/opencs/scene-exterior-status-4.png b/files/opencs/scene-exterior-status-4.png new file mode 100644 index 0000000000..c984989b54 Binary files /dev/null and b/files/opencs/scene-exterior-status-4.png differ diff --git a/files/opencs/scene-exterior-status-5.png b/files/opencs/scene-exterior-status-5.png new file mode 100644 index 0000000000..dee32f443e Binary files /dev/null and b/files/opencs/scene-exterior-status-5.png differ diff --git a/files/opencs/scene-exterior-status-6.png b/files/opencs/scene-exterior-status-6.png new file mode 100644 index 0000000000..877b005e4f Binary files /dev/null and b/files/opencs/scene-exterior-status-6.png differ diff --git a/files/opencs/scene-exterior-status-7.png b/files/opencs/scene-exterior-status-7.png new file mode 100644 index 0000000000..6a8afeff72 Binary files /dev/null and b/files/opencs/scene-exterior-status-7.png differ diff --git a/files/opencs/scene-view-status-0.png b/files/opencs/scene-view-status-0.png index b98230f013..2b2bb4d294 100644 Binary files a/files/opencs/scene-view-status-0.png and b/files/opencs/scene-view-status-0.png differ diff --git a/files/opencs/scene-view-status-1.png b/files/opencs/scene-view-status-1.png index 43b597c01e..ba18877b9b 100644 Binary files a/files/opencs/scene-view-status-1.png and b/files/opencs/scene-view-status-1.png differ diff --git a/files/opencs/scene-view-status-10.png b/files/opencs/scene-view-status-10.png index ca511b9c87..d6e23431eb 100644 Binary files a/files/opencs/scene-view-status-10.png and b/files/opencs/scene-view-status-10.png differ diff --git a/files/opencs/scene-view-status-11.png b/files/opencs/scene-view-status-11.png index 3028217ba9..21a77c5330 100644 Binary files a/files/opencs/scene-view-status-11.png and b/files/opencs/scene-view-status-11.png differ diff --git a/files/opencs/scene-view-status-12.png b/files/opencs/scene-view-status-12.png index 974519c549..efe608d2e3 100644 Binary files a/files/opencs/scene-view-status-12.png and b/files/opencs/scene-view-status-12.png differ diff --git a/files/opencs/scene-view-status-13.png b/files/opencs/scene-view-status-13.png index 4c075704af..2fe7d40253 100644 Binary files a/files/opencs/scene-view-status-13.png and b/files/opencs/scene-view-status-13.png differ diff --git a/files/opencs/scene-view-status-14.png b/files/opencs/scene-view-status-14.png index 1e6d726da2..56f77199f2 100644 Binary files a/files/opencs/scene-view-status-14.png and b/files/opencs/scene-view-status-14.png differ diff --git a/files/opencs/scene-view-status-15.png b/files/opencs/scene-view-status-15.png index 3a2073628d..5dab523c66 100644 Binary files a/files/opencs/scene-view-status-15.png and b/files/opencs/scene-view-status-15.png differ diff --git a/files/opencs/scene-view-status-16.png b/files/opencs/scene-view-status-16.png index d0fb540476..2ff5aba752 100644 Binary files a/files/opencs/scene-view-status-16.png and b/files/opencs/scene-view-status-16.png differ diff --git a/files/opencs/scene-view-status-17.png b/files/opencs/scene-view-status-17.png index 9d95cc8553..1b30b6be13 100644 Binary files a/files/opencs/scene-view-status-17.png and b/files/opencs/scene-view-status-17.png differ diff --git a/files/opencs/scene-view-status-18.png b/files/opencs/scene-view-status-18.png index 0a9013aed3..363d396cb4 100644 Binary files a/files/opencs/scene-view-status-18.png and b/files/opencs/scene-view-status-18.png differ diff --git a/files/opencs/scene-view-status-19.png b/files/opencs/scene-view-status-19.png index d57fab4465..31f90a5d38 100644 Binary files a/files/opencs/scene-view-status-19.png and b/files/opencs/scene-view-status-19.png differ diff --git a/files/opencs/scene-view-status-2.png b/files/opencs/scene-view-status-2.png index 2c892213b3..06e4cd3931 100644 Binary files a/files/opencs/scene-view-status-2.png and b/files/opencs/scene-view-status-2.png differ diff --git a/files/opencs/scene-view-status-20.png b/files/opencs/scene-view-status-20.png index 2be69333c2..5420953eda 100644 Binary files a/files/opencs/scene-view-status-20.png and b/files/opencs/scene-view-status-20.png differ diff --git a/files/opencs/scene-view-status-21.png b/files/opencs/scene-view-status-21.png index f5ad223409..7344f3195a 100644 Binary files a/files/opencs/scene-view-status-21.png and b/files/opencs/scene-view-status-21.png differ diff --git a/files/opencs/scene-view-status-22.png b/files/opencs/scene-view-status-22.png index afb0c7dd58..99977a1ccb 100644 Binary files a/files/opencs/scene-view-status-22.png and b/files/opencs/scene-view-status-22.png differ diff --git a/files/opencs/scene-view-status-23.png b/files/opencs/scene-view-status-23.png index bee128d24d..6c428e5a81 100644 Binary files a/files/opencs/scene-view-status-23.png and b/files/opencs/scene-view-status-23.png differ diff --git a/files/opencs/scene-view-status-24.png b/files/opencs/scene-view-status-24.png index 677a564481..903a4d5a4b 100644 Binary files a/files/opencs/scene-view-status-24.png and b/files/opencs/scene-view-status-24.png differ diff --git a/files/opencs/scene-view-status-25.png b/files/opencs/scene-view-status-25.png index 151278eff6..dd7ad5458f 100644 Binary files a/files/opencs/scene-view-status-25.png and b/files/opencs/scene-view-status-25.png differ diff --git a/files/opencs/scene-view-status-26.png b/files/opencs/scene-view-status-26.png index 3ddeed8f13..fca0ed8e90 100644 Binary files a/files/opencs/scene-view-status-26.png and b/files/opencs/scene-view-status-26.png differ diff --git a/files/opencs/scene-view-status-27.png b/files/opencs/scene-view-status-27.png index 93058121ef..1a888bff97 100644 Binary files a/files/opencs/scene-view-status-27.png and b/files/opencs/scene-view-status-27.png differ diff --git a/files/opencs/scene-view-status-28.png b/files/opencs/scene-view-status-28.png index 5008026a15..5b11fda91e 100644 Binary files a/files/opencs/scene-view-status-28.png and b/files/opencs/scene-view-status-28.png differ diff --git a/files/opencs/scene-view-status-29.png b/files/opencs/scene-view-status-29.png index 7b355fe13d..2118906388 100644 Binary files a/files/opencs/scene-view-status-29.png and b/files/opencs/scene-view-status-29.png differ diff --git a/files/opencs/scene-view-status-3.png b/files/opencs/scene-view-status-3.png index 5157e170f5..7d23409f7c 100644 Binary files a/files/opencs/scene-view-status-3.png and b/files/opencs/scene-view-status-3.png differ diff --git a/files/opencs/scene-view-status-30.png b/files/opencs/scene-view-status-30.png index 0712a54820..825bc772c6 100644 Binary files a/files/opencs/scene-view-status-30.png and b/files/opencs/scene-view-status-30.png differ diff --git a/files/opencs/scene-view-status-4.png b/files/opencs/scene-view-status-4.png index f29445f5ab..82057190da 100644 Binary files a/files/opencs/scene-view-status-4.png and b/files/opencs/scene-view-status-4.png differ diff --git a/files/opencs/scene-view-status-5.png b/files/opencs/scene-view-status-5.png index e77290f689..79532bf0f7 100644 Binary files a/files/opencs/scene-view-status-5.png and b/files/opencs/scene-view-status-5.png differ diff --git a/files/opencs/scene-view-status-6.png b/files/opencs/scene-view-status-6.png index a2f9a8a99f..2b1d385263 100644 Binary files a/files/opencs/scene-view-status-6.png and b/files/opencs/scene-view-status-6.png differ diff --git a/files/opencs/scene-view-status-7.png b/files/opencs/scene-view-status-7.png index ce4e20e005..9916683309 100644 Binary files a/files/opencs/scene-view-status-7.png and b/files/opencs/scene-view-status-7.png differ diff --git a/files/opencs/scene-view-status-8.png b/files/opencs/scene-view-status-8.png index f0c499832e..406649aac7 100644 Binary files a/files/opencs/scene-view-status-8.png and b/files/opencs/scene-view-status-8.png differ diff --git a/files/opencs/scene-view-status-9.png b/files/opencs/scene-view-status-9.png index b30c3eec51..cf83a9f7c3 100644 Binary files a/files/opencs/scene-view-status-9.png and b/files/opencs/scene-view-status-9.png differ diff --git a/files/openmw.cfg b/files/openmw.cfg index 3a9bd87628..199e094036 100644 --- a/files/openmw.cfg +++ b/files/openmw.cfg @@ -2,7 +2,6 @@ # Modifications should be done on the user openmw.cfg file instead # (see: https://wiki.openmw.org/index.php?title=Paths) -data="?mw?Data Files" data=${MORROWIND_DATA_FILES} data-local="?userdata?data" resources=${OPENMW_RESOURCE_FILES} diff --git a/files/openmw.cfg.local b/files/openmw.cfg.local index 71cd3bfbfa..0640ec843f 100644 --- a/files/openmw.cfg.local +++ b/files/openmw.cfg.local @@ -3,7 +3,6 @@ # (see: https://wiki.openmw.org/index.php?title=Paths) data="?global?data" -data="?mw?Data Files" data=./data data-local="?userdata?data" resources=./resources diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 49c9c54191..3d2cd49bfd 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -14,7 +14,7 @@ [Camera] # Near clipping plane (>0.0, e.g. 0.01 to 18.0). -near clip = 5.0 +near clip = 1 # Cull objects smaller than one pixel. small feature culling = true @@ -37,6 +37,40 @@ first person field of view = 55.0 # dramatically affect performance, see documentation for details. exterior cell load distance = 1 +# Preload cells in a background thread. All settings starting with 'preload' have no effect unless this is enabled. +preload enabled = true + +# Preload adjacent cells when moving close to an exterior cell border. +preload exterior grid = true + +# Preload possible fast travel destinations. +preload fast travel = false + +# Preload the locations that doors lead to. +preload doors = true + +# Preloading distance threshold +preload distance = 1000 + +# Controls whether or not the nodes/collision shapes are pre-"instanced" (i.e. cloned) when a cell is preloaded. +# Enabling this option slightly reduces the time it takes to transition into a preloaded cell, but also results in higher memory usage +# proportional to the number of cells that are preloaded. +preload instances = true + +# The minimum amount of cells in the preload cache before unused cells start to get thrown out (see "preload cell expiry delay"). +# This value should be lower or equal to 'preload cell cache max'. +preload cell cache min = 12 + +# The maximum amount of cells in the preload cache. A too high value could cause you to run out of memory. +# You may need to reduce this setting when running lots of mods or high-res texture replacers. +preload cell cache max = 20 + +# How long to keep preloaded cells in cache after they're no longer referenced/required (in seconds) +preload cell expiry delay = 5 + +# How long to keep models/textures/collision shapes in cache after they're no longer referenced/required (in seconds) +cache expiry delay = 5 + [Map] # Size of each exterior cell in pixels in the world map. (e.g. 12 to 24). @@ -47,6 +81,10 @@ global map cell size = 18 # cell, 256 is 1/8 cell. See documentation for details. (e.g. 64 to 256). local map hud widget size = 256 +# Enables Fog of War rendering on the HUD map. Default is Off since with default settings +# the map is so small that the fog would not obscure anything, just darken the edges slightly. +local map hud fog of war = false + # Resolution of local map in GUI window in pixels. See documentation # for details which may affect cell load performance. (e.g. 128 to 1024). local map resolution = 256 @@ -54,6 +92,11 @@ local map resolution = 256 # Size of local map in GUI window in pixels. (e.g. 256 to 1024). local map widget size = 512 +# Similar to "[Cells] exterior cell load distance", controls +# how many cells are rendered on the local map. Values higher than the default +# may result in longer loading times. +local map cell distance = 1 + [GUI] # Scales GUI window and widget size. (<1.0 is smaller, >1.0 is larger). @@ -119,6 +162,59 @@ texture min filter = linear # Texture mipmap type. (none, nearest, or linear). texture mipmap = nearest +[Shaders] + +# Force rendering with shaders. By default, only bump-mapped objects will use shaders. +# Enabling this option may cause slightly different visuals if the "clamp lighting" option +# is set to false. Otherwise, there should not be a visual difference. +force shaders = false + +# Force the use of per pixel lighting. By default, only bump mapped objects use per-pixel lighting. +# Has no effect if the 'force shaders' option is false. +# Enabling per-pixel lighting can result in visual differences to the original MW engine. It is not +# recommended to enable this option when using vanilla Morrowind files, because certain lights in Morrowind +# rely on vertex lighting to look as intended. +force per pixel lighting = false + +# Restrict the amount of lighting that an object can receive to a maximum of (1,1,1). +# Only affects objects that render with shaders (see 'force shaders' option). Always affects terrain. +# Setting this option to 'true' results in fixed-function compatible lighting, but the lighting +# may appear 'dull' and there might be color shifts. +# Setting this option to 'false' results in more realistic lighting. +clamp lighting = true + +# If this option is enabled, normal maps are automatically recognized and used if they are named appropriately +# (see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds). +# If this option is disabled, normal maps are only used if they are explicitely listed within the mesh file (.nif or .osg file). +# Affects objects. +auto use object normal maps = false + +# If this option is enabled, specular maps are automatically recognized and used if they are named appropriately +# (see 'specular map pattern', e.g. for a base texture foo.dds, the specular map texture would have to be named foo_spec.dds). +# If this option is disabled, normal maps are only used if they are explicitely listed within the mesh file (.osg file, not supported in .nif files). +# Affects objects. +auto use object specular maps = false + +# See 'auto use object normal maps'. Affects terrain. +auto use terrain normal maps = false + +# If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture +# must contain the layer color in the RGB channel (as usual), and a specular multiplier in the alpha channel. +auto use terrain specular maps = false + +# The filename pattern to probe for when detecting normal maps (see 'auto use object normal maps', 'auto use terrain normal maps') +normal map pattern = _n + +# Alternative filename pattern to probe for when detecting normal maps. Files with this pattern are expected to include 'height' in the alpha channel. +# This height is used for parallax effects. Works for both terrain and objects. +normal height map pattern = _nh + +# The filename pattern to probe for when detecting object specular maps (see 'auto use object specular maps') +specular map pattern = _spec + +# The filename pattern to probe for when detecting terrain specular maps (see 'auto use terrain specular maps') +terrain specular map pattern = _diffusespec + [Input] # Capture control of the cursor prevent movement outside the window. @@ -236,53 +332,8 @@ rtt size = 512 # Enable refraction which affects visibility through water plane. refraction = false -[Objects] - -# Enable shaders for objects other than water. Unused. -shaders = true - -[Terrain] - -# Use shaders for terrain? Unused. -shader = true - -# Distant land is rendered? Unused. -distant land = false - -[Shadows] - -# Enable shadows. Other shadow settings disabled if false. Unused. -enabled = false - -# Size of the shadow textures in pixels. Unused. (e.g. 256 to 2048). -texture size = 1024 - -# Actors cast shadows. Unused. -actor shadows = true - -# Static objects cast shadows. Unused. -statics shadows = true - -# Terrain cast shadows. Unused. -terrain shadows = true - -# Miscellaneous objects cast shadows. Unused. -misc shadows = true - -# Debugging of shadows. Unused. -debug = false - -# Fraction of distance after which shadow starts to fade out. Unused. -fade start = 0.8 - -# Split shadow maps, allowing for a larger shadow distance. Unused. -split = false - -# Distance for shadows if not split. Smaller is poorer. Unused. -shadow distance = 1300 - -# Distance for shadows if split. Unused. -split shadow distance = 14000 +# Draw NPCs and creatures on water reflections. +reflect actors = false [Windows] diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index fc4706c1f6..0738b5783c 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -6,6 +6,12 @@ set(SHADER_FILES water_vertex.glsl water_fragment.glsl water_nm.png + objects_vertex.glsl + objects_fragment.glsl + terrain_vertex.glsl + terrain_fragment.glsl + lighting.glsl + parallax.glsl ) copy_all_files(${CMAKE_CURRENT_SOURCE_DIR} ${DDIR} "${SHADER_FILES}") diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl new file mode 100644 index 0000000000..08b369b5bd --- /dev/null +++ b/files/shaders/lighting.glsl @@ -0,0 +1,53 @@ +#define MAX_LIGHTS 8 + +vec4 doLighting(vec3 viewPos, vec3 viewNormal, vec4 vertexColor) +{ + vec3 lightDir; + float d; + +#if @colorMode == 2 + vec4 diffuse = vertexColor; + vec3 ambient = vertexColor.xyz; +#else + vec4 diffuse = gl_FrontMaterial.diffuse; + vec3 ambient = gl_FrontMaterial.ambient.xyz; +#endif + vec4 lightResult = vec4(0.0, 0.0, 0.0, diffuse.a); + + for (int i=0; i 0) ? -1.f : 1.f); + adjustedDiffuseUV += offset; // only offset diffuse for now, other textures are more likely to be using a completely different UV set + + // TODO: check not working as the same UV buffer is being bound to different targets + // if diffuseMapUV == normalMapUV +#if 1 + // fetch a new normal using updated coordinates + normalTex = texture2D(normalMap, adjustedDiffuseUV); + viewNormal = gl_NormalMatrix * normalize(tbnTranspose * (normalTex.xyz * 2.0 - 1.0)); +#endif + +#endif + +#if @diffuseMap + gl_FragData[0] = texture2D(diffuseMap, adjustedDiffuseUV); +#else + gl_FragData[0] = vec4(1.0, 1.0, 1.0, 1.0); +#endif + +#if @detailMap + gl_FragData[0].xyz *= texture2D(detailMap, detailMapUV).xyz * 2.0; +#endif + +#if @darkMap + gl_FragData[0].xyz *= texture2D(darkMap, darkMapUV).xyz; +#endif + +#if @decalMap + vec4 decalTex = texture2D(decalMap, decalMapUV); + gl_FragData[0].xyz = mix(gl_FragData[0].xyz, decalTex.xyz, decalTex.a); +#endif + +#if !PER_PIXEL_LIGHTING + gl_FragData[0] *= lighting; +#else + gl_FragData[0] *= doLighting(passViewPos, normalize(viewNormal), passColor); +#endif + +#if @emissiveMap + gl_FragData[0].xyz += texture2D(emissiveMap, emissiveMapUV).xyz; +#endif + + +#if @envMap + +#if @normalMap + // if using normal map + env map, take advantage of per-pixel normals for texCoordGen + vec3 viewVec = normalize(passViewPos.xyz); + vec3 r = reflect( viewVec, viewNormal ); + float m = 2.0 * sqrt( r.x*r.x + r.y*r.y + (r.z+1.0)*(r.z+1.0) ); + vec2 texCoordGen = vec2(r.x/m + 0.5, r.y/m + 0.5); + gl_FragData[0].xyz += texture2D(envMap, texCoordGen).xyz * envMapColor.xyz; +#else + gl_FragData[0].xyz += texture2D(envMap, envMapUV).xyz * envMapColor.xyz; +#endif + +#endif + +#if @specularMap + vec4 specTex = texture2D(specularMap, specularMapUV); + float shininess = specTex.a * 255; + vec3 matSpec = specTex.xyz; +#else + float shininess = gl_FrontMaterial.shininess; + vec3 matSpec = gl_FrontMaterial.specular.xyz; +#endif + + gl_FragData[0].xyz += getSpecular(normalize(viewNormal), normalize(passViewPos.xyz), shininess, matSpec); + + float fogValue = clamp((depth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); + gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); +} diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl new file mode 100644 index 0000000000..e140cd6ad3 --- /dev/null +++ b/files/shaders/objects_vertex.glsl @@ -0,0 +1,102 @@ +#version 120 + +#if @diffuseMap +varying vec2 diffuseMapUV; +#endif + +#if @darkMap +varying vec2 darkMapUV; +#endif + +#if @detailMap +varying vec2 detailMapUV; +#endif + +#if @decalMap +varying vec2 decalMapUV; +#endif + +#if @emissiveMap +varying vec2 emissiveMapUV; +#endif + +#if @normalMap +varying vec2 normalMapUV; +varying vec4 passTangent; +#endif + +#if @envMap +varying vec2 envMapUV; +#endif + +#if @specularMap +varying vec2 specularMapUV; +#endif + +varying float depth; + +#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) + +#if !PER_PIXEL_LIGHTING +varying vec4 lighting; +#else +varying vec4 passColor; +#endif +varying vec3 passViewPos; +varying vec3 passNormal; + +#include "lighting.glsl" + +void main(void) +{ + gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; + depth = gl_Position.z; + + vec4 viewPos = (gl_ModelViewMatrix * gl_Vertex); + gl_ClipVertex = viewPos; + vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz); + +#if @envMap + vec3 viewVec = normalize(viewPos.xyz); + vec3 r = reflect( viewVec, viewNormal ); + float m = 2.0 * sqrt( r.x*r.x + r.y*r.y + (r.z+1.0)*(r.z+1.0) ); + envMapUV = vec2(r.x/m + 0.5, r.y/m + 0.5); +#endif + +#if @diffuseMap + diffuseMapUV = (gl_TextureMatrix[@diffuseMapUV] * gl_MultiTexCoord@diffuseMapUV).xy; +#endif + +#if @darkMap + darkMapUV = (gl_TextureMatrix[@darkMapUV] * gl_MultiTexCoord@darkMapUV).xy; +#endif + +#if @detailMap + detailMapUV = (gl_TextureMatrix[@detailMapUV] * gl_MultiTexCoord@detailMapUV).xy; +#endif + +#if @decalMap + decalMapUV = (gl_TextureMatrix[@decalMapUV] * gl_MultiTexCoord@decalMapUV).xy; +#endif + +#if @emissiveMap + emissiveMapUV = (gl_TextureMatrix[@emissiveMapUV] * gl_MultiTexCoord@emissiveMapUV).xy; +#endif + +#if @normalMap + normalMapUV = (gl_TextureMatrix[@normalMapUV] * gl_MultiTexCoord@normalMapUV).xy; + passTangent = gl_MultiTexCoord7.xyzw; +#endif + +#if @specularMap + specularMapUV = (gl_TextureMatrix[@specularMapUV] * gl_MultiTexCoord@specularMapUV).xy; +#endif + +#if !PER_PIXEL_LIGHTING + lighting = doLighting(viewPos.xyz, viewNormal, gl_Color); +#else + passColor = gl_Color; +#endif + passViewPos = viewPos.xyz; + passNormal = gl_Normal.xyz; +} diff --git a/files/shaders/parallax.glsl b/files/shaders/parallax.glsl new file mode 100644 index 0000000000..433dbcd3e4 --- /dev/null +++ b/files/shaders/parallax.glsl @@ -0,0 +1,8 @@ +#define PARALLAX_SCALE 0.04 +#define PARALLAX_BIAS -0.02 + +vec2 getParallaxOffset(vec3 eyeDir, mat3 tbnTranspose, float height, float flipY) +{ + vec3 TSeyeDir = normalize(eyeDir * tbnTranspose); + return vec2(TSeyeDir.x, TSeyeDir.y * flipY) * ( height * PARALLAX_SCALE + PARALLAX_BIAS ); +} diff --git a/files/shaders/terrain_fragment.glsl b/files/shaders/terrain_fragment.glsl new file mode 100644 index 0000000000..134e838c08 --- /dev/null +++ b/files/shaders/terrain_fragment.glsl @@ -0,0 +1,85 @@ +#version 120 + +varying vec2 uv; + +uniform sampler2D diffuseMap; + +#if @normalMap +uniform sampler2D normalMap; +#endif + +#if @blendMap +uniform sampler2D blendMap; +#endif + +varying float depth; + +#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) + +#if !PER_PIXEL_LIGHTING +varying vec4 lighting; +#else +varying vec4 passColor; +#endif +varying vec3 passViewPos; +varying vec3 passNormal; + +#include "lighting.glsl" +#include "parallax.glsl" + +void main() +{ + vec2 adjustedUV = (gl_TextureMatrix[0] * vec4(uv, 0.0, 1.0)).xy; + +#if @normalMap + vec4 normalTex = texture2D(normalMap, adjustedUV); + + vec3 normalizedNormal = normalize(passNormal); + vec3 tangent = vec3(1.0, 0.0, 0.0); + vec3 binormal = normalize(cross(tangent, normalizedNormal)); + tangent = normalize(cross(normalizedNormal, binormal)); // note, now we need to re-cross to derive tangent again because it wasn't orthonormal + mat3 tbnTranspose = mat3(tangent, binormal, normalizedNormal); + + vec3 viewNormal = normalize(gl_NormalMatrix * (tbnTranspose * (normalTex.xyz * 2.0 - 1.0))); +#else + vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); +#endif + +#if @parallax + vec3 cameraPos = (gl_ModelViewMatrixInverse * vec4(0,0,0,1)).xyz; + vec3 objectPos = (gl_ModelViewMatrixInverse * vec4(passViewPos, 1)).xyz; + vec3 eyeDir = normalize(cameraPos - objectPos); + adjustedUV += getParallaxOffset(eyeDir, tbnTranspose, normalTex.a, 1.f); + + // update normal using new coordinates + normalTex = texture2D(normalMap, adjustedUV); + viewNormal = normalize(gl_NormalMatrix * (tbnTranspose * (normalTex.xyz * 2.0 - 1.0))); +#endif + + vec4 diffuseTex = texture2D(diffuseMap, adjustedUV); + gl_FragData[0] = vec4(diffuseTex.xyz, 1.0); + +#if @blendMap + vec2 blendMapUV = (gl_TextureMatrix[1] * vec4(uv, 0.0, 1.0)).xy; + gl_FragData[0].a *= texture2D(blendMap, blendMapUV).a; +#endif + +#if !PER_PIXEL_LIGHTING + gl_FragData[0] *= lighting; +#else + gl_FragData[0] *= doLighting(passViewPos, normalize(viewNormal), passColor); +#endif + +#if @specularMap + float shininess = 128; // TODO: make configurable + vec3 matSpec = vec3(diffuseTex.a, diffuseTex.a, diffuseTex.a); +#else + float shininess = gl_FrontMaterial.shininess; + vec3 matSpec = gl_FrontMaterial.specular.xyz; +#endif + + gl_FragData[0].xyz += getSpecular(normalize(viewNormal), normalize(passViewPos), shininess, matSpec); + + float fogValue = clamp((depth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); + gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); +} diff --git a/files/shaders/terrain_vertex.glsl b/files/shaders/terrain_vertex.glsl new file mode 100644 index 0000000000..e2a180153a --- /dev/null +++ b/files/shaders/terrain_vertex.glsl @@ -0,0 +1,36 @@ +#version 120 + +varying vec2 uv; +varying float depth; + +#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) + +#if !PER_PIXEL_LIGHTING +varying vec4 lighting; +#else +varying vec4 passColor; +#endif +varying vec3 passViewPos; +varying vec3 passNormal; + +#include "lighting.glsl" + +void main(void) +{ + gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; + depth = gl_Position.z; + + vec4 viewPos = (gl_ModelViewMatrix * gl_Vertex); + gl_ClipVertex = viewPos; + +#if !PER_PIXEL_LIGHTING + vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz); + lighting = doLighting(viewPos.xyz, viewNormal, gl_Color); +#else + passColor = gl_Color; +#endif + passNormal = gl_Normal.xyz; + passViewPos = viewPos.xyz; + + uv = gl_MultiTexCoord0.xy; +}