diff --git a/.gitignore b/.gitignore index 672c3ec18..827c0a072 100644 --- a/.gitignore +++ b/.gitignore @@ -81,6 +81,5 @@ moc_*.cxx *ui_playpage.h *.[ao] *.so -gamecontrollerdb.txt openmw.appdata.xml venv/ diff --git a/.travis.yml b/.travis.yml index d173ef6f2..02cc7cf20 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,7 +24,7 @@ addons: - llvm-toolchain-precise-3.6 packages: [ # Dev - clang-3.6, libunshield-dev, libtinyxml-dev, + cmake, clang-3.6, libunshield-dev, libtinyxml-dev, # Tests libgtest-dev, google-mock, # Boost diff --git a/AUTHORS.md b/AUTHORS.md index 839a04e42..155d017f3 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -129,6 +129,7 @@ Programmers Radu-Marius Popovici (rpopovici) Rafael Moura (dhustkoder) rdimesio + rexelion riothamus Rob Cutmore (rcutmore) Robert MacGregor (Ragora) @@ -152,6 +153,7 @@ Programmers Sylvain Thesnieres (Garvek) t6 terrorfisch + thegriglat Thomas Luppi (Digmaster) Will Herrmann (Thunderforge) Tom Mason (wheybags) diff --git a/CHANGELOG.md b/CHANGELOG.md index b390169c6..ac2a5472e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,140 @@ +0.43.0 +------ + + Bug #815: Different settings cause inconsistent underwater visibility + Bug #1452: autosave is not executed when waiting + Bug #1555: Closing containers with spacebar doesn't work after touching an item + Bug #1692: Can't close container when item is "held" + Bug #2405: Maximum distance for guards attacking hostile creatures is incorrect + Bug #2445: Spellcasting can be interrupted + Bug #2489: Keeping map open not persisted between saves + Bug #2594: 1st person view uses wrong body texture with Better bodies + Bug #2628: enablestatreviewmenu command doen't read race, class and sign values from current game + Bug #2639: Attacking flag isn't reset upon reloading + Bug #2698: Snow and rain VFX move with the player + Bug #2704: Some creature swim animations not being used + Bug #2789: Potential risk of misunderstanding using the colored "owned" crosshair feature + Bug #3045: Settings containing '#' cannot be loaded + Bug #3097: Drop() doesn't work when an item is held (with the mouse) + Bug #3110: GetDetected doesn't work without a reference + Bug #3126: Framerate nosedives when adjusting dialogue window size + Bug #3243: Ampersand in configuration files isn't escaped automatically + Bug #3365: Wrong water reflection along banks + Bug #3441: Golden saint always dispelling soul trap / spell priority issue + Bug #3528: Disposing of corpses breaks quests + Bug #3531: No FPS limit when playing bink videos even though "framerate limit" is set in settings.cfg + Bug #3647: Multi-effect spells play audio louder than in Vanilla + Bug #3656: NPCs forget where their place in the world is + Bug #3665: Music transitions are too abrupt + Bug #3679: Spell cast effect should disappear after using rest command + Bug #3684: Merchants do not restock empty soul gems if they acquire filled ones. + Bug #3694: Wrong magicka bonus applied on character creation + Bug #3706: Guards don't try to arrest the player if attacked + Bug #3709: Editor: Camera is not positioned correctly on mode switches related to orbital mode + Bug #3720: Death counter not cleaned of non-existing IDs when loading a game + Bug #3744: "Greater/lesser or equal" operators are not parsed when their signs are swapped + Bug #3749: Yagrum Bagarn moves to different position on encountering + Bug #3766: DisableLevitation does not remove visuals of preexisting effect + Bug #3787: Script commands in result box for voiced dialogue are ignored + Bug #3793: OpenMW tries to animate animated references even when they are disabled + Bug #3794: Default sound buffer size is too small for mods + Bug #3796: Mod 'Undress for me' doesn't work: NPCs re-equip everything + Bug #3798: tgm command behaviour differs from vanilla + Bug #3804: [Mod] Animated Morrowind: some animations do not loop correctly + Bug #3805: Slight enchant miscalculation + Bug #3826: Rendering problems with an image in a letter + Bug #3833: [Mod] Windows Glow: windows textures are much darker than in original game + Bug #3835: Bodyparts with multiple NiTriShapes are not handled correctly + Bug #3839: InventoryStore::purgeEffect() removes only first effect with argument ID + Bug #3843: Wrong jumping fatigue loss calculations + Bug #3850: Boethiah's voice is distorted underwater + Bug #3851: NPCs and player say things while underwater + Bug #3864: Crash when exiting to Khartag point from Ilunibi + Bug #3878: Swapping soul gems while enchanting allows constant effect enchantments using any soul gem + Bug #3879: Dialogue option: Go to jail, persists beyond quickload + Bug #3891: Journal displays empty entries + Bug #3892: Empty space before dialogue entry display + Bug #3898: (mod) PositionCell in dialogue results closes dialogue window + Bug #3906: "Could not find Data Files location" dialog can appear multiple times + Bug #3908: [Wizard] User gets stuck if they cancel out of installing from a CD + Bug #3909: Morrowind Content Language dropdown is the only element on the right half of the Settings window + Bug #3910: Launcher window can be resized so that it cuts off the scroll + Bug #3915: NC text key on nifs doesn't work + Bug #3919: Closing inventory while cursor hovers over spell (or other magic menu item) produces left click sound + Bug #3922: Combat AI should avoid enemy hits when casts Self-ranged spells + Bug #3934: [macOS] Copy/Paste from system clipboard uses Control key instead of Command key + Bug #3935: Incorrect attack strength for AI actors + Bug #3937: Combat AI: enchanted weapons have too high rating + Bug #3942: UI sounds are distorted underwater + Bug #3943: CPU/GPU usage should stop when the game is minimised + Bug #3944: Attempting to sell stolen items back to their owner does not remove them from your inventory + Bug #3955: Player's avatar rendering issues + Bug #3956: EditEffectDialog: Cancel button does not update a Range button and an Area slider properly + Bug #3957: Weird bodypart rendering if a node has reserved name + Bug #3960: Clothes with high cost (> 32768) are not handled properly + Bug #3963: When on edge of being burdened the condition doesn't lower as you run. + Bug #3971: Editor: Incorrect colour field in cell table + Bug #3974: Journal page turning doesn't produce sounds + Bug #3978: Instant opening and closing happens when using a Controller with Menus/Containers + Bug #3981: Lagging when spells are cast, especially noticeable on new landmasses such as Tamriel Rebuilt + Bug #3982: Down sounds instead of Up ones are played when trading + Bug #3987: NPCs attack after some taunting with no "Goodbye" + Bug #3991: Journal can still be opened at main menu + Bug #3995: Dispel cancels every temporary magic effect + Bug #4002: Build broken on OpenBSD with clang + Bug #4003: Reduce Render Area of Inventory Doll to Fit Within Border + Bug #4004: Manis Virmaulese attacks without saying anything + Bug #4010: AiWander: "return to the spawn position" feature does not work properly + Bug #4016: Closing menus with spacebar will still send certain assigned actions through afterwards + Bug #4017: GetPCRunning and GetPCSneaking should check that the PC is actually moving + Bug #4024: Poor music track distribution + Bug #4025: Custom spell with copy-pasted name always sorts to top of spell list + Bug #4027: Editor: OpenMW-CS misreports its own name as "OpenCS", under Mac OS + Bug #4033: Archers don't attack if the arrows have run out and there is no other weapon + Bug #4037: Editor: New greetings do not work in-game. + Bug #4049: Reloading a saved game while falling prevents damage + Bug #4056: Draw animation should not be played when player equips a new weapon + Bug #4074: Editor: Merging of LAND/LTEX records + Bug #4076: Disposition bar is not updated when "goodbye" selected in dialogue + Bug #4079: Alchemy skill increases do not take effect until next batch + Bug #4093: GetResistFire, getResistFrost and getResistShock doesn't work as in vanilla + Bug #4094: Level-up messages for levels past 20 are hardcoded not to be used + Bug #4095: Error in framelistener when take all items from a dead corpse + Bug #4096: Messagebox with the "%0.f" format should use 0 digit precision + Bug #4104: Cycling through weapons does not skip broken ones + Bug #4105: birthsign generation menu does not show full details + Bug #4107: Editor: Left pane in Preferences window is too narrow + Bug #4112: Inventory sort order is inconsistent + Bug #4113: 'Resolution not supported in fullscreen' message is inconvenient + Bug #4131: Pickpocketing behaviour is different from vanilla + Bug #4155: NPCs don't equip a second ring in some cases + Bug #4156: Snow doesn't create water ripples + Bug #4165: NPCs autoequip new clothing with the same price + Feature #452: Rain-induced water ripples + Feature #824: Fading for doors and teleport commands + Feature #933: Editor: LTEX record table + Feature #936: Editor: LAND record table + Feature #1374: AI: Resurface to breathe + Feature #2320: ess-Importer: convert projectiles + Feature #2509: Editor: highlighting occurrences of a word in a script + Feature #2748: Editor: Should use one resource manager per document + Feature #2834: Have openMW's UI remember what menu items were 'pinned' across boots. + Feature #2923: Option to show the damage of the arrows through tooltip. + Feature #3099: Disabling inventory while dragging an item forces you to drop it + Feature #3274: Editor: Script Editor - Shortcuts and context menu options for commenting code out and uncommenting code respectively + Feature #3275: Editor: User Settings- Add an option to reset settings to their default status (per category / all) + Feature #3400: Add keyboard shortcuts for menus + Feature #3492: Show success rate while enchanting + Feature #3530: Editor: Reload data files + Feature #3682: Editor: Default key binding reset + Feature #3921: Combat AI: aggro priorities + Feature #3941: Allow starting at an unnamed exterior cell with --start + Feature #3952: Add Visual Studio 2017 support + Feature #3953: Combat AI: use "WhenUsed" enchantments + Feature #4082: Leave the stack of ingredients or potions grabbed after using an ingredient/potion + Task #2258: Windows installer: launch OpenMW tickbox + Task #4152: The Windows CI script is moving files around that CMake should be dealing with + 0.42.0 ------ diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 39cb37bed..ee48c1f68 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -320,9 +320,9 @@ if [ -z $SKIP_DOWNLOAD ]; then "ffmpeg-3.2.4-dev-win${BITS}.zip" # MyGUI - download "MyGUI 3.2.3-git" \ - "http://www.lysator.liu.se/~ace/OpenMW/deps/MyGUI-3.2.3-git-msvc${MSVC_YEAR}-win${BITS}.7z" \ - "MyGUI-3.2.3-git-msvc${MSVC_YEAR}-win${BITS}.7z" + download "MyGUI 3.2.2" \ + "http://www.lysator.liu.se/~ace/OpenMW/deps/MyGUI-3.2.2-msvc${MSVC_YEAR}-win${BITS}.7z" \ + "MyGUI-3.2.2-msvc${MSVC_YEAR}-win${BITS}.7z" # OpenAL download "OpenAL-Soft 1.17.2" \ @@ -350,9 +350,9 @@ if [ -z $SKIP_DOWNLOAD ]; then fi # SDL2 - download "SDL 2.0.4" \ - "https://www.libsdl.org/release/SDL2-devel-2.0.4-VC.zip" \ - "SDL2-2.0.4.zip" + download "SDL 2.0.7" \ + "https://www.libsdl.org/release/SDL2-devel-2.0.7-VC.zip" \ + "SDL2-2.0.7.zip" fi cd .. #/.. @@ -474,20 +474,20 @@ cd $DEPS echo # MyGUI -printf "MyGUI 3.2.3-git... " +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 3" 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.3-git-msvc${MSVC_YEAR}-win${BITS}.7z" $STRIP - mv "MyGUI-3.2.3-git-msvc${MSVC_YEAR}-win${BITS}" MyGUI + eval 7z x -y "${DEPS}/MyGUI-3.2.2-msvc${MSVC_YEAR}-win${BITS}.7z" $STRIP + mv "MyGUI-3.2.2-msvc${MSVC_YEAR}-win${BITS}" MyGUI fi export MYGUI_HOME="$(real_pwd)/MyGUI" @@ -632,18 +632,18 @@ cd $DEPS echo # SDL2 -printf "SDL 2.0.4... " +printf "SDL 2.0.7... " { - if [ -d SDL2-2.0.4 ]; then + if [ -d SDL2-2.0.7 ]; then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then - rm -rf SDL2-2.0.4 - eval 7z x -y SDL2-2.0.4.zip $STRIP + rm -rf SDL2-2.0.7 + eval 7z x -y SDL2-2.0.7.zip $STRIP fi - export SDL2DIR="$(real_pwd)/SDL2-2.0.4" + export SDL2DIR="$(real_pwd)/SDL2-2.0.7" - add_runtime_dlls "$(pwd)/SDL2-2.0.4/lib/x${ARCHSUFFIX}/SDL2.dll" + add_runtime_dlls "$(pwd)/SDL2-2.0.7/lib/x${ARCHSUFFIX}/SDL2.dll" echo Done. } diff --git a/CMakeLists.txt b/CMakeLists.txt index b47678e7d..2923612fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,16 +24,8 @@ if (USE_QT) set_property(CACHE DESIRED_QT_VERSION PROPERTY STRINGS 4 5) endif() -if (APPLE) - # OS X build process relies on this fix: https://github.com/Kitware/CMake/commit/3df5147043d83aa09acd5c9ce31d5c602efb99db - cmake_minimum_required(VERSION 3.1.0) -elseif (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() +# set the minimum required version across the board +cmake_minimum_required(VERSION 3.1.0) project(OpenMW) @@ -62,7 +54,7 @@ endif() message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) -set(OPENMW_VERSION_MINOR 42) +set(OPENMW_VERSION_MINOR 43) set(OPENMW_VERSION_RELEASE 0) set(OPENMW_VERSION_COMMITHASH "") @@ -336,6 +328,12 @@ copy_resource_file(${OpenMW_SOURCE_DIR}/files/opencs/defaultfilters configure_resource_file(${OpenMW_SOURCE_DIR}/files/gamecontrollerdb.txt "${OpenMW_BINARY_DIR}" "gamecontrollerdb.txt") +configure_resource_file(${OpenMW_SOURCE_DIR}/files/gamecontrollerdb_204.txt + "${OpenMW_BINARY_DIR}" "gamecontrollerdb_204.txt") + +configure_resource_file(${OpenMW_SOURCE_DIR}/files/gamecontrollerdb_205.txt + "${OpenMW_BINARY_DIR}" "gamecontrollerdb_205.txt") + if (NOT WIN32 AND NOT APPLE) configure_file(${OpenMW_SOURCE_DIR}/files/openmw.desktop "${OpenMW_BINARY_DIR}/openmw.desktop") @@ -346,6 +344,7 @@ if (NOT WIN32 AND NOT APPLE) endif() # CXX Compiler settings +set(CMAKE_CXX_STANDARD 11) if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wundef -Wno-unused-parameter -std=c++11 -pedantic -Wno-long-long") add_definitions( -DBOOST_NO_CXX11_SCOPED_ENUMS=ON ) @@ -412,7 +411,7 @@ IF(NOT WIN32 AND NOT APPLE) #ENDIF(BUILD_MYGUI_PLUGIN) # Install licenses - INSTALL(FILES "docs/license/DejaVu Font License.txt" DESTINATION "${LICDIR}" ) + INSTALL(FILES "files/mygui/DejaVu Font License.txt" DESTINATION "${LICDIR}" ) # Install icon and desktop file INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "openmw") @@ -428,6 +427,8 @@ IF(NOT WIN32 AND NOT APPLE) INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" DESTINATION "${SYSCONFDIR}" RENAME "openmw.cfg" COMPONENT "openmw") INSTALL(FILES "${OpenMW_BINARY_DIR}/resources/version" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") INSTALL(FILES "${OpenMW_BINARY_DIR}/gamecontrollerdb.txt" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") + INSTALL(FILES "${OpenMW_BINARY_DIR}/gamecontrollerdb_204.txt" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") + INSTALL(FILES "${OpenMW_BINARY_DIR}/gamecontrollerdb_205.txt" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") IF(BUILD_OPENCS) INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw-cs.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "opencs") @@ -443,15 +444,22 @@ if(WIN32) FILE(GLOB dll_files_release "${OpenMW_BINARY_DIR}/Release/*.dll") INSTALL(FILES ${dll_files_debug} DESTINATION "." CONFIGURATIONS Debug) INSTALL(FILES ${dll_files_release} DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) - INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" DESTINATION "." RENAME "openmw.cfg") + INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/openmw.cfg.install" DESTINATION "." RENAME "openmw.cfg" CONFIGURATIONS Debug) + INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/openmw.cfg.install" DESTINATION "." RENAME "openmw.cfg" CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) INSTALL(FILES "${OpenMW_SOURCE_DIR}/CHANGELOG.md" DESTINATION "." RENAME "CHANGELOG.txt") INSTALL(FILES "${OpenMW_SOURCE_DIR}/README.md" DESTINATION "." RENAME "README.txt") + INSTALL(FILES "${OpenMW_SOURCE_DIR}/LICENSE" DESTINATION "." RENAME "LICENSE.txt") INSTALL(FILES - "${OpenMW_SOURCE_DIR}/Docs/license/GPL3.txt" - "${OpenMW_SOURCE_DIR}/Docs/license/DejaVu Font License.txt" - "${OpenMW_BINARY_DIR}/settings-default.cfg" - "${OpenMW_BINARY_DIR}/gamecontrollerdb.txt" + "${OpenMW_SOURCE_DIR}/files/mygui/DejaVu Font License.txt" DESTINATION ".") + INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/settings-default.cfg" DESTINATION "." CONFIGURATIONS Debug) + INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/settings-default.cfg" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) + INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/gamecontrollerdb.txt" DESTINATION "." CONFIGURATIONS Debug) + INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/gamecontrollerdb.txt" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) + INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/gamecontrollerdb_204.txt" DESTINATION "." CONFIGURATIONS Debug) + INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/gamecontrollerdb_204.txt" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) + INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/gamecontrollerdb_205.txt" DESTINATION "." CONFIGURATIONS Debug) + INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/gamecontrollerdb_205.txt" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) if(BUILD_MYGUI_PLUGIN) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Debug/Plugin_MyGUI_OpenMW_Resources.dll" DESTINATION "." CONFIGURATIONS Debug) @@ -463,7 +471,9 @@ if(WIN32) INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Release/platforms" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) ENDIF() - INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION ".") + INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Debug/resources" DESTINATION "." CONFIGURATIONS Debug) + INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Release/resources" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) + FILE(GLOB plugin_dir_debug "${OpenMW_BINARY_DIR}/Debug/osgPlugins-*") FILE(GLOB plugin_dir_release "${OpenMW_BINARY_DIR}/Release/osgPlugins-*") INSTALL(DIRECTORY ${plugin_dir_debug} DESTINATION "." CONFIGURATIONS Debug) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b5a7423d2..6c82d1dfd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,6 +33,7 @@ Furthermore, we advise to: * Feel free to submit incomplete pull requests. Even if the work can not be merged yet, pull requests are a great place to collect early feedback. Just make sure to mark it as *[Incomplete]* or *[Do not merge yet]* in the title. * If you plan on contributing often, please read the [Developer Reference](https://wiki.openmw.org/index.php?title=Developer_Reference) on our wiki, especially the [Policies and Standards](https://wiki.openmw.org/index.php?title=Policies_and_Standards). * Make sure each of your changes has a clear objective. Unnecessary changes may lead to merge conflicts, clutter the commit history and slow down review. Code formatting 'fixes' should be avoided, unless you were already changing that particular line anyway. +* Reference the bug / feature ticket(s) in your commit message (e.g. 'Bug #123') to make it easier to keep track of what we changed for what reason. Our bugtracker will show those commits next to the ticket. If your commit message includes 'Fixes #123', that bug/feature will automatically be set to 'Closed' when your commit is merged. Guidelines for original engine "fixes" ================================= diff --git a/docs/license/GPL3.txt b/LICENSE similarity index 99% rename from docs/license/GPL3.txt rename to LICENSE index 94a9ed024..9cecc1d46 100644 --- a/docs/license/GPL3.txt +++ b/LICENSE @@ -631,8 +631,8 @@ to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. - - Copyright (C) + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: - Copyright (C) + {project} Copyright (C) {year} {fullname} This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. diff --git a/README.md b/README.md index aa6ae47b4..0b5f63f93 100644 --- a/README.md +++ b/README.md @@ -7,13 +7,13 @@ OpenMW is a recreation of the engine for the popular role-playing game Morrowind OpenMW also comes with OpenMW-CS, a replacement for Morrowind's TES Construction Set. -* Version: 0.42.0 -* License: GPLv3 (see [docs/license/GPL3.txt](https://github.com/OpenMW/openmw/blob/master/docs/license/GPL3.txt) for more information) +* Version: 0.43.0 +* License: GPLv3 (see [LICENSE](https://github.com/OpenMW/openmw/blob/master/LICENSE) for more information) * Website: http://www.openmw.org * IRC: #openmw on irc.freenode.net Font Licenses: -* DejaVuLGCSansMono.ttf: custom (see docs/license/DejaVu Font License.txt for more information) +* DejaVuLGCSansMono.ttf: custom (see [files/mygui/DejaVu Font License.txt](https://github.com/OpenMW/openmw/blob/master/files/mygui/DejaVu%20Font%20License.txt) for more information) Current Status -------------- diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index aac076404..aec8c2533 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -6,6 +6,7 @@ set(LAUNCHER playpage.cpp textslotmsgbox.cpp settingspage.cpp + advancedpage.cpp utils/profilescombobox.cpp utils/textinputdialog.cpp @@ -21,6 +22,7 @@ set(LAUNCHER_HEADER playpage.hpp textslotmsgbox.hpp settingspage.hpp + advancedpage.hpp utils/profilescombobox.hpp utils/textinputdialog.hpp @@ -35,6 +37,7 @@ set(LAUNCHER_HEADER_MOC playpage.hpp textslotmsgbox.hpp settingspage.hpp + advancedpage.hpp utils/textinputdialog.hpp utils/profilescombobox.hpp @@ -49,6 +52,7 @@ set(LAUNCHER_UI ${CMAKE_SOURCE_DIR}/files/ui/playpage.ui ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui ${CMAKE_SOURCE_DIR}/files/ui/settingspage.ui + ${CMAKE_SOURCE_DIR}/files/ui/advancedpage.ui ) source_group(launcher FILES ${LAUNCHER} ${LAUNCHER_HEADER}) diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp new file mode 100644 index 000000000..3ba378599 --- /dev/null +++ b/apps/launcher/advancedpage.cpp @@ -0,0 +1,89 @@ +#include "advancedpage.hpp" + +#include + +Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent) + : QWidget(parent) + , mCfgMgr(cfg) + , mEngineSettings(engineSettings) +{ + setObjectName ("AdvancedPage"); + setupUi(this); + + loadSettings(); +} + +bool Launcher::AdvancedPage::loadSettings() +{ + // Game Settings + loadSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game"); + loadSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); + loadSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game"); + loadSettingBool(showEffectDurationCheckBox, "show effect duration", "Game"); + loadSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game"); + loadSettingBool(showMeleeInfoCheckBox, "show melee info", "Game"); + loadSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game"); + + // Expected values are (0, 1, 2, 3) + int showOwnedIndex = mEngineSettings.getInt("show owned", "Game"); + // Match the index with the option. Will default to 0 if invalid. + if (showOwnedIndex >= 0 && showOwnedIndex <= 3) + showOwnedComboBox->setCurrentIndex(showOwnedIndex); + + // Input Settings + loadSettingBool(allowThirdPersonZoomCheckBox, "allow third person zoom", "Input"); + loadSettingBool(grabCursorCheckBox, "grab cursor", "Input"); + loadSettingBool(toggleSneakCheckBox, "toggle sneak", "Input"); + + // Other Settings + loadSettingBool(timePlayedCheckbox, "timeplayed", "Saves"); + + QString screenshotFormatString = QString::fromStdString(mEngineSettings.getString("screenshot format", "General")).toUpper(); + if (screenshotFormatComboBox->findText(screenshotFormatString) == -1) + screenshotFormatComboBox->addItem(screenshotFormatString); + screenshotFormatComboBox->setCurrentIndex(screenshotFormatComboBox->findText(screenshotFormatString)); + + return true; +} + +void Launcher::AdvancedPage::saveSettings() +{ + // Ensure we only set the new settings if they changed. This is to avoid cluttering the + // user settings file (which by definition should only contain settings the user has touched) + + // Game Settings + saveSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game"); + saveSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); + saveSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game"); + saveSettingBool(showEffectDurationCheckBox, "show effect duration", "Game"); + saveSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game"); + saveSettingBool(showMeleeInfoCheckBox, "show melee info", "Game"); + saveSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game"); + + int showOwnedCurrentIndex = showOwnedComboBox->currentIndex(); + if (showOwnedCurrentIndex != mEngineSettings.getInt("show owned", "Game")) + mEngineSettings.setInt("show owned", "Game", showOwnedCurrentIndex); + + // Input Settings + saveSettingBool(allowThirdPersonZoomCheckBox, "allow third person zoom", "Input"); + saveSettingBool(grabCursorCheckBox, "grab cursor", "Input"); + saveSettingBool(toggleSneakCheckBox, "toggle sneak", "Input"); + + // Other Settings + saveSettingBool(timePlayedCheckbox, "timeplayed", "Saves"); + + std::string screenshotFormatString = screenshotFormatComboBox->currentText().toLower().toStdString(); + if (screenshotFormatString != mEngineSettings.getString("screenshot format", "General")) + mEngineSettings.setString("screenshot format", "General", screenshotFormatString); +} + +void Launcher::AdvancedPage::loadSettingBool(QCheckBox *checkbox, const std::string &setting, const std::string &group) { + if (mEngineSettings.getBool(setting, group)) + checkbox->setCheckState(Qt::Checked); +} + +void Launcher::AdvancedPage::saveSettingBool(QCheckBox *checkbox, const std::string &setting, const std::string &group) { + bool cValue = checkbox->checkState(); + if (cValue != mEngineSettings.getBool(setting, group)) + mEngineSettings.setBool(setting, group, cValue); +} \ No newline at end of file diff --git a/apps/launcher/advancedpage.hpp b/apps/launcher/advancedpage.hpp new file mode 100644 index 000000000..a8361c98e --- /dev/null +++ b/apps/launcher/advancedpage.hpp @@ -0,0 +1,32 @@ +#ifndef ADVANCEDPAGE_H +#define ADVANCEDPAGE_H + +#include + +#include "ui_advancedpage.h" + +#include + +namespace Files { struct ConfigurationManager; } + +namespace Launcher +{ + class AdvancedPage : public QWidget, private Ui::AdvancedPage + { + Q_OBJECT + + public: + AdvancedPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent = 0); + + bool loadSettings(); + void saveSettings(); + + private: + Files::ConfigurationManager &mCfgMgr; + Settings::Manager &mEngineSettings; + + void loadSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group); + void saveSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group); + }; +} +#endif diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 31da90d2f..1a210ccc5 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -2,9 +2,7 @@ #include -#include #include -#include #include #include #include @@ -12,8 +10,6 @@ #include #include #include -#include -#include #include @@ -21,6 +17,7 @@ #include "graphicspage.hpp" #include "datafilespage.hpp" #include "settingspage.hpp" +#include "advancedpage.hpp" using namespace Process; @@ -104,6 +101,12 @@ void Launcher::MainDialog::createIcons() settingsButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom); settingsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); + QListWidgetItem *advancedButton = new QListWidgetItem(iconWidget); + advancedButton->setIcon(QIcon::fromTheme("emblem-system")); + advancedButton->setText(tr("Advanced")); + advancedButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom); + advancedButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); + connect(iconWidget, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), this, SLOT(changePage(QListWidgetItem*,QListWidgetItem*))); @@ -116,6 +119,7 @@ void Launcher::MainDialog::createPages() mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this); mGraphicsPage = new GraphicsPage(mCfgMgr, mEngineSettings, this); mSettingsPage = new SettingsPage(mCfgMgr, mGameSettings, mLauncherSettings, this); + mAdvancedPage = new AdvancedPage(mCfgMgr, mEngineSettings, this); // Set the combobox of the play page to imitate the combobox on the datafilespage mPlayPage->setProfilesModel(mDataFilesPage->profilesModel()); @@ -126,6 +130,7 @@ void Launcher::MainDialog::createPages() pagesWidget->addWidget(mDataFilesPage); pagesWidget->addWidget(mGraphicsPage); pagesWidget->addWidget(mSettingsPage); + pagesWidget->addWidget(mAdvancedPage); // Select the first page iconWidget->setCurrentItem(iconWidget->item(0), QItemSelectionModel::Select); @@ -245,6 +250,9 @@ bool Launcher::MainDialog::reloadSettings() if (!mGraphicsPage->loadSettings()) return false; + if (!mAdvancedPage->loadSettings()) + return false; + return true; } @@ -483,6 +491,7 @@ bool Launcher::MainDialog::writeSettings() mDataFilesPage->saveSettings(); mGraphicsPage->saveSettings(); mSettingsPage->saveSettings(); + mAdvancedPage->saveSettings(); QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str()); QDir dir(userPath); diff --git a/apps/launcher/maindialog.hpp b/apps/launcher/maindialog.hpp index 8d0d61b8f..6d3871b7c 100644 --- a/apps/launcher/maindialog.hpp +++ b/apps/launcher/maindialog.hpp @@ -30,6 +30,7 @@ namespace Launcher class DataFilesPage; class UnshieldThread; class SettingsPage; + class AdvancedPage; enum FirstRunDialogResult { @@ -88,6 +89,7 @@ namespace Launcher GraphicsPage *mGraphicsPage; DataFilesPage *mDataFilesPage; SettingsPage *mSettingsPage; + AdvancedPage *mAdvancedPage; Process::ProcessInvoker *mGameInvoker; Process::ProcessInvoker *mWizardInvoker; diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 777bb2d52..b9279bf91 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -252,7 +252,8 @@ endif() if (WIN32) target_link_libraries(openmw-cs ${Boost_LOCALE_LIBRARY}) INSTALL(TARGETS openmw-cs RUNTIME DESTINATION ".") - INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw-cs.cfg" DESTINATION ".") + INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/openmw-cs.cfg" DESTINATION "." CONFIGURATIONS Debug) + INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/openmw-cs.cfg" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) endif() if (MSVC) diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 1f84c5a87..8a9dad7f3 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -169,18 +169,25 @@ void CSMPrefs::State::declare() "list go to the first/last item"); declareCategory ("3D Scene Input"); + + declareDouble ("navi-wheel-factor", "Camera Zoom Sensitivity", 8).setRange(-100.0, 100.0); + declareDouble ("s-navi-sensitivity", "Secondary Camera Movement Sensitivity", 50.0).setRange(-1000.0, 1000.0); + declareSeparator(); + 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-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); + declareSeparator(); + + 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 ("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); + declareBool ("navi-orbit-const-roll", "Keep camera roll constant for orbital camera", true); declareSeparator(); + declareBool ("context-select", "Context Sensitive Selection", false); declareDouble ("drag-factor", "Mouse sensitivity during drag operations", 1.0). setRange (0.001, 100.0); @@ -192,6 +199,14 @@ void CSMPrefs::State::declare() setRange (0.001, 100.0); declareDouble ("rotate-factor", "Free rotation factor", 0.007).setPrecision(4).setRange(0.0001, 0.1); + declareCategory ("Rendering"); + declareInt ("camera-fov", "Camera FOV", 90).setRange(10, 170); + declareBool ("camera-ortho", "Orthographic projection for camera", false); + declareInt ("camera-ortho-size", "Orthographic projection size parameter", 100). + setTooltip("Size of the orthographic frustum, greater value will allow the camera to see more of the world."). + setRange(10, 10000); + declareDouble ("object-marker-alpha", "Object Marker Transparency", 0.5).setPrecision(2).setRange(0,1); + declareCategory ("Tooltips"); declareBool ("scene", "Show Tooltips in 3D scenes", true); declareBool ("scene-hide-basic", "Hide basic 3D scenes tooltips", false); diff --git a/apps/opencs/view/prefs/dialogue.cpp b/apps/opencs/view/prefs/dialogue.cpp index 960ca74bc..1f5772f18 100644 --- a/apps/opencs/view/prefs/dialogue.cpp +++ b/apps/opencs/view/prefs/dialogue.cpp @@ -84,7 +84,6 @@ CSVPrefs::Dialogue::~Dialogue() void CSVPrefs::Dialogue::closeEvent (QCloseEvent *event) { QMainWindow::closeEvent (event); - CSMPrefs::State::get().save(); } diff --git a/apps/opencs/view/render/cameracontroller.cpp b/apps/opencs/view/render/cameracontroller.cpp index 7b802b0ef..37f439fd3 100644 --- a/apps/opencs/view/render/cameracontroller.cpp +++ b/apps/opencs/view/render/cameracontroller.cpp @@ -668,16 +668,25 @@ namespace CSVRender mInitialized = true; } + + void OrbitCameraController::setConstRoll(bool enabled) + { + mConstRoll = enabled; + } void OrbitCameraController::rotateHorizontal(double value) { osg::Vec3d eye, center, up; getCamera()->getViewMatrixAsLookAt(eye, center, up); + osg::Vec3d absoluteUp = osg::Vec3(0,0,1); - osg::Quat rotation = osg::Quat(value, up); + osg::Quat rotation = osg::Quat(value, mConstRoll ? absoluteUp : up); osg::Vec3d oldOffset = eye - mCenter; osg::Vec3d newOffset = rotation * oldOffset; + if (mConstRoll) + up = rotation * up; + getCamera()->setViewMatrixAsLookAt(mCenter + newOffset, mCenter, up); } @@ -687,9 +696,14 @@ namespace CSVRender getCamera()->getViewMatrixAsLookAt(eye, center, up); osg::Vec3d forward = center - eye; - osg::Quat rotation = osg::Quat(value, up ^ forward); + osg::Vec3d axis = up ^ forward; + + osg::Quat rotation = osg::Quat(value,axis); osg::Vec3d oldOffset = eye - mCenter; osg::Vec3d newOffset = rotation * oldOffset; + + if (mConstRoll) + up = rotation * up; getCamera()->setViewMatrixAsLookAt(mCenter + newOffset, mCenter, up); } diff --git a/apps/opencs/view/render/cameracontroller.hpp b/apps/opencs/view/render/cameracontroller.hpp index a2ebba51a..658e572c5 100644 --- a/apps/opencs/view/render/cameracontroller.hpp +++ b/apps/opencs/view/render/cameracontroller.hpp @@ -160,6 +160,8 @@ namespace CSVRender /// \brief Flag controller to be re-initialized. void reset(); + void setConstRoll(bool enable); + private: void initialize(); @@ -181,6 +183,8 @@ namespace CSVRender double mOrbitSpeed; double mOrbitSpeedMult; + bool mConstRoll; + private slots: void naviPrimary(bool active); diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index 522057097..dae6467c5 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -21,6 +22,7 @@ #include "../../model/world/universalid.hpp" #include "../../model/world/commandmacro.hpp" #include "../../model/world/cellcoordinates.hpp" +#include "../../model/prefs/state.hpp" #include #include @@ -220,7 +222,7 @@ osg::ref_ptr CSVRender::Object::makeMoveOrScaleMarker (int axis) for (int i=0; i<2; ++i) { - float length = i ? shaftLength : 0; + float length = i ? shaftLength : MarkerShaftWidth; vertices->push_back (getMarkerPosition (-MarkerShaftWidth/2, -MarkerShaftWidth/2, length, axis)); vertices->push_back (getMarkerPosition (-MarkerShaftWidth/2, MarkerShaftWidth/2, length, axis)); @@ -285,15 +287,15 @@ osg::ref_ptr CSVRender::Object::makeMoveOrScaleMarker (int axis) 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)); + axis==2 ? 1.0f : 0.2f, mMarkerTransparency)); 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)); + axis==2 ? 1.0f : 0.0f, mMarkerTransparency)); geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX); - geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF); + setupCommonMarkerState(geometry); osg::ref_ptr geode (new osg::Geode); geode->addDrawable (geometry); @@ -305,11 +307,11 @@ osg::ref_ptr CSVRender::Object::makeRotateMarker (int axis) { const float Pi = 3.14159265f; - const float InnerRadius = mBaseNode->getBound().radius(); + const float InnerRadius = std::max(MarkerShaftBaseLength, mBaseNode->getBound().radius()); const float OuterRadius = InnerRadius + MarkerShaftWidth; const float SegmentDistance = 100.f; - const size_t SegmentCount = std::min(64, std::max(8, (int)(OuterRadius * 2 * Pi / SegmentDistance))); + const size_t SegmentCount = std::min(64, std::max(24, (int)(OuterRadius * 2 * Pi / SegmentDistance))); const size_t VerticesPerSegment = 4; const size_t IndicesPerSegment = 24; @@ -334,6 +336,9 @@ osg::ref_ptr CSVRender::Object::makeRotateMarker (int axis) osg::ref_ptr primitives = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, IndexCount); + // prevent some depth collision issues from overlaps + osg::Vec3f offset = getMarkerPosition(0, MarkerShaftWidth/4, 0, axis); + for (size_t i = 0; i < SegmentCount; ++i) { size_t index = i * VerticesPerSegment; @@ -344,13 +349,17 @@ osg::ref_ptr CSVRender::Object::makeRotateMarker (int axis) float outerX = OuterRadius * std::cos(i * Angle); float outerY = OuterRadius * std::sin(i * Angle); - vertices->at(index++) = getMarkerPosition(innerX, innerY, MarkerShaftWidth / 2, axis); - vertices->at(index++) = getMarkerPosition(innerX, innerY, -MarkerShaftWidth / 2, axis); - vertices->at(index++) = getMarkerPosition(outerX, outerY, MarkerShaftWidth / 2, axis); - vertices->at(index++) = getMarkerPosition(outerX, outerY, -MarkerShaftWidth / 2, axis); + vertices->at(index++) = getMarkerPosition(innerX, innerY, MarkerShaftWidth / 2, axis) + offset; + vertices->at(index++) = getMarkerPosition(innerX, innerY, -MarkerShaftWidth / 2, axis) + offset; + vertices->at(index++) = getMarkerPosition(outerX, outerY, MarkerShaftWidth / 2, axis) + offset; + vertices->at(index++) = getMarkerPosition(outerX, outerY, -MarkerShaftWidth / 2, axis) + offset; } - colors->at(0) = osg::Vec4f (axis==0 ? 1.0f : 0.2f, axis==1 ? 1.0f : 0.2f, axis==2 ? 1.0f : 0.2f, 1.0f); + colors->at(0) = osg::Vec4f ( + axis==0 ? 1.0f : 0.2f, + axis==1 ? 1.0f : 0.2f, + axis==2 ? 1.0f : 0.2f, + mMarkerTransparency); for (size_t i = 0; i < SegmentCount; ++i) { @@ -374,7 +383,7 @@ osg::ref_ptr CSVRender::Object::makeRotateMarker (int axis) geometry->setColorArray(colors, osg::Array::BIND_OVERALL); geometry->addPrimitiveSet(primitives); - geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF); + setupCommonMarkerState(geometry); osg::ref_ptr geode = new osg::Geode(); geode->addDrawable (geometry); @@ -382,6 +391,15 @@ osg::ref_ptr CSVRender::Object::makeRotateMarker (int axis) return geode; } +void CSVRender::Object::setupCommonMarkerState(osg::ref_ptr geometry) +{ + osg::ref_ptr state = geometry->getOrCreateStateSet(); + state->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + state->setMode(GL_BLEND, osg::StateAttribute::ON); + + state->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); +} + osg::Vec3f CSVRender::Object::getMarkerPosition (float x, float y, float z, int axis) { switch (axis) @@ -399,7 +417,7 @@ osg::Vec3f CSVRender::Object::getMarkerPosition (float x, float y, float z, int 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), - mScaleOverride (1), mOverrideFlags (0), mSubMode (-1) + mScaleOverride (1), mOverrideFlags (0), mSubMode (-1), mMarkerTransparency(0.5f) { mRootNode = new osg::PositionAttitudeTransform; @@ -453,6 +471,7 @@ void CSVRender::Object::setSelected(bool selected) else mRootNode->addChild(mBaseNode); + mMarkerTransparency = CSMPrefs::get()["Rendering"]["object-marker-alpha"].toDouble(); updateMarker(); } @@ -629,6 +648,12 @@ void CSVRender::Object::setScale (float scale) adjustTransform(); } +void CSVRender::Object::setMarkerTransparency(float value) +{ + mMarkerTransparency = value; + updateMarker(); +} + void CSVRender::Object::apply (CSMWorld::CommandMacro& commands) { const CSMWorld::RefCollection& collection = mData.getReferences(); diff --git a/apps/opencs/view/render/object.hpp b/apps/opencs/view/render/object.hpp index e14697e62..3e54093d3 100644 --- a/apps/opencs/view/render/object.hpp +++ b/apps/opencs/view/render/object.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -96,6 +97,7 @@ namespace CSVRender int mOverrideFlags; osg::ref_ptr mMarker[3]; int mSubMode; + float mMarkerTransparency; /// Not implemented Object (const Object&); @@ -121,6 +123,9 @@ namespace CSVRender osg::ref_ptr makeMoveOrScaleMarker (int axis); osg::ref_ptr makeRotateMarker (int axis); + /// Sets up a stateset with properties common to all marker types. + void setupCommonMarkerState(osg::ref_ptr geometry); + osg::Vec3f getMarkerPosition (float x, float y, float z, int axis); public: @@ -179,6 +184,8 @@ namespace CSVRender /// Set override scale void setScale (float scale); + void setMarkerTransparency(float value); + /// Apply override changes via command and end edit mode void apply (CSMWorld::CommandMacro& commands); diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 11c7f5926..f24a9de50 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -56,6 +56,7 @@ RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f) traits->vsync = false; mView = new osgViewer::View; + updateCameraParameters( traits->width / static_cast(traits->height) ); osg::ref_ptr window = new osgQt::GraphicsWindowQt(traits.get()); QLayout* layout = new QHBoxLayout(this); @@ -66,7 +67,6 @@ RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f) 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 ); SceneUtil::LightManager* lightMgr = new SceneUtil::LightManager; lightMgr->setStartLight(1); @@ -188,6 +188,8 @@ SceneWidget::SceneWidget(std::shared_ptr resourceSyste mOrbitCamControl->setPickingMask(Mask_Reference | Mask_Terrain); + mOrbitCamControl->setConstRoll( CSMPrefs::get()["3D Scene Input"]["navi-orbit-const-roll"].isTrue() ); + // we handle lighting manually mView->setLightingMode(osgViewer::View::NO_LIGHT); @@ -370,6 +372,40 @@ void SceneWidget::settingChanged (const CSMPrefs::Setting *setting) { mOrbitCamControl->setOrbitSpeedMultiplier(setting->toDouble()); } + else if (*setting=="3D Scene Input/navi-orbit-const-roll") + { + mOrbitCamControl->setConstRoll(setting->isTrue()); + } + else if (*setting=="Rendering/camera-fov" || + *setting=="Rendering/camera-ortho" || + *setting=="Rendering/camera-ortho-size") + { + updateCameraParameters(); + } +} + +void RenderWidget::updateCameraParameters(double overrideAspect) +{ + const float nearDist = 1.0; + const float farDist = 1000.0; + + if (CSMPrefs::get()["Rendering"]["camera-ortho"].isTrue()) + { + const float size = CSMPrefs::get()["Rendering"]["camera-ortho-size"].toInt(); + const float aspect = overrideAspect >= 0.0 ? overrideAspect : (width() / static_cast(height())); + const float halfH = size * 10.0; + const float halfW = halfH * aspect; + + mView->getCamera()->setProjectionMatrixAsOrtho( + -halfW, halfW, -halfH, halfH, nearDist, farDist); + } + else + { + mView->getCamera()->setProjectionMatrixAsPerspective( + CSMPrefs::get()["Rendering"]["camera-fov"].toInt(), + static_cast(width())/static_cast(height()), + nearDist, farDist); + } } void SceneWidget::selectNavigationMode (const std::string& mode) diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp index cc3191c81..13a109a9b 100644 --- a/apps/opencs/view/render/scenewidget.hpp +++ b/apps/opencs/view/render/scenewidget.hpp @@ -64,6 +64,8 @@ namespace CSVRender osg::ref_ptr mView; osg::ref_ptr mRootNode; + void updateCameraParameters(double overrideAspect = -1.0); + QTimer mTimer; protected slots: diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 325fa5f6d..af53c86f0 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -51,6 +51,7 @@ CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidg , mToolTipPos (-1, -1) , mShowToolTips(false) , mToolTipDelay(0) + , mInConstructor(true) { setAcceptDrops(true); @@ -114,6 +115,8 @@ CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidg CSMPrefs::Shortcut* abortShortcut = new CSMPrefs::Shortcut("scene-edit-abort", this); connect(abortShortcut, SIGNAL(activated()), this, SLOT(abortDrag())); + + mInConstructor = false; } CSVRender::WorldspaceWidget::~WorldspaceWidget () @@ -128,6 +131,17 @@ void CSVRender::WorldspaceWidget::settingChanged (const CSMPrefs::Setting *setti mDragWheelFactor = setting->toDouble(); else if (*setting=="3D Scene Input/drag-shift-factor") mDragShiftFactor = setting->toDouble(); + else if (*setting=="Rendering/object-marker-alpha" && !mInConstructor) + { + float alpha = setting->toDouble(); + // getSelection is virtual, thus this can not be called from the constructor + auto selection = getSelection(Mask_Reference); + for (osg::ref_ptr tag : selection) + { + if (auto objTag = dynamic_cast(tag.get())) + objTag->mObject->setMarkerTransparency(alpha); + } + } else if (*setting=="Tooltips/scene-delay") mToolTipDelay = setting->toInt(); else if (*setting=="Tooltips/scene") diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 08b97e1be..9160ca47e 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -65,6 +65,7 @@ namespace CSVRender QPoint mToolTipPos; bool mShowToolTips; int mToolTipDelay; + bool mInConstructor; public: diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 43952afdb..e869e2915 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -159,9 +159,8 @@ if (ANDROID) dl z ${OPENSCENEGRAPH_LIBRARIES} - ${OSG_PLUGINS} + freetype jpeg - gif png ) endif (ANDROID) @@ -190,6 +189,8 @@ if(APPLE) configure_file("${OpenMW_BINARY_DIR}/settings-default.cfg" ${BUNDLE_RESOURCES_DIR} COPYONLY) configure_file("${OpenMW_BINARY_DIR}/openmw.cfg" ${BUNDLE_RESOURCES_DIR} COPYONLY) configure_file("${OpenMW_BINARY_DIR}/gamecontrollerdb.txt" ${BUNDLE_RESOURCES_DIR} COPYONLY) + configure_file("${OpenMW_BINARY_DIR}/gamecontrollerdb_204.txt" ${BUNDLE_RESOURCES_DIR} COPYONLY) + configure_file("${OpenMW_BINARY_DIR}/gamecontrollerdb_205.txt" ${BUNDLE_RESOURCES_DIR} COPYONLY) add_custom_command(TARGET openmw POST_BUILD diff --git a/apps/openmw/android_main.c b/apps/openmw/android_main.c index 8cd69e8f0..3f28afa1b 100644 --- a/apps/openmw/android_main.c +++ b/apps/openmw/android_main.c @@ -1,6 +1,9 @@ +int stderr = 0; // Hack: fix linker error -#ifdef __ANDROID__ #include "SDL_main.h" +#include +#include +#include /******************************************************************************* Functions called by JNI @@ -14,13 +17,37 @@ extern int argcData; extern const char **argvData; void releaseArgv(); + +int Java_org_libsdl_app_SDLActivity_getMouseX(JNIEnv *env, jclass cls, jobject obj) { + int ret = 0; + SDL_GetMouseState(&ret, NULL); + return ret; +} + + +int Java_org_libsdl_app_SDLActivity_getMouseY(JNIEnv *env, jclass cls, jobject obj) { + int ret = 0; + SDL_GetMouseState(NULL, &ret); + return ret; +} + +int Java_org_libsdl_app_SDLActivity_isMouseShown(JNIEnv *env, jclass cls, jobject obj) { + return SDL_ShowCursor(SDL_QUERY); +} + + int Java_org_libsdl_app_SDLActivity_nativeInit(JNIEnv* env, jclass cls, jobject obj) { + setenv("OPENMW_DECOMPRESS_TEXTURES", "1", 1); + SDL_Android_Init(env, cls); SDL_SetMainReady(); + // On Android, we use a virtual controller with guid="Virtual" + SDL_GameControllerAddMapping("5669727475616c000000000000000000,Virtual,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4"); + /* Run the application code! */ int status; @@ -33,5 +60,3 @@ int Java_org_libsdl_app_SDLActivity_nativeInit(JNIEnv* env, jclass cls, return status; } -#endif /* __ANDROID__ */ - diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 499158fd4..5052a13cc 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -79,12 +79,14 @@ void OMW::Engine::executeLocalScripts() } } -void OMW::Engine::frame(float frametime) +bool OMW::Engine::frame(float frametime) { try { mStartTick = mViewer->getStartTick(); + mEnvironment.setFrameDuration(frametime); + // update input mEnvironment.getInputManager()->update(frametime, false); @@ -92,7 +94,7 @@ void OMW::Engine::frame(float frametime) // If we are not currently rendering, then RenderItems will not be reused resulting in a memory leak upon changing widget textures (fixed in MyGUI 3.3.2), // and destroyed widgets will not be deleted (not fixed yet, https://github.com/MyGUI/mygui/issues/21) if (!mEnvironment.getInputManager()->isWindowVisible()) - return; + return false; // sound if (mUseSound) @@ -187,8 +189,9 @@ void OMW::Engine::frame(float frametime) } catch (const std::exception& e) { - std::cerr << "Error in framelistener: " << e.what() << std::endl; + std::cerr << "Error in frame: " << e.what() << std::endl; } + return true; } OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) @@ -474,8 +477,20 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) } // find correct path to the game controller bindings - const std::string localdefault = mCfgMgr.getLocalPath().string() + "/gamecontrollerdb.txt"; - const std::string globaldefault = mCfgMgr.getGlobalPath().string() + "/gamecontrollerdb.txt"; + // File format for controller mappings is different for SDL <= 2.0.4, 2.0.5, and >= 2.0.6 + SDL_version linkedSdlVersion; + SDL_GetVersion(&linkedSdlVersion); + std::string controllerFileName; + if (linkedSdlVersion.major == 2 && linkedSdlVersion.minor == 0 && linkedSdlVersion.patch <= 4) { + controllerFileName = "gamecontrollerdb_204.txt"; + } else if (linkedSdlVersion.major == 2 && linkedSdlVersion.minor == 0 && linkedSdlVersion.patch == 5) { + controllerFileName = "gamecontrollerdb_205.txt"; + } else { + controllerFileName = "gamecontrollerdb.txt"; + } + + const std::string localdefault = mCfgMgr.getLocalPath().string() + "/" + controllerFileName; + const std::string globaldefault = mCfgMgr.getGlobalPath().string() + "/" + controllerFileName; std::string gameControllerdb; if (boost::filesystem::exists(localdefault)) gameControllerdb = localdefault; @@ -689,17 +704,9 @@ void OMW::Engine::go() frameTimer.setStartTick(); dt = std::min(dt, 0.2); - bool guiActive = mEnvironment.getWindowManager()->isGuiMode(); - if (!guiActive) - simulationTime += dt; - mViewer->advance(simulationTime); - mEnvironment.setFrameDuration(dt); - - frame(dt); - - if (!mEnvironment.getInputManager()->isWindowVisible()) + if (!frame(dt)) { OpenThreads::Thread::microSleep(5000); continue; @@ -712,6 +719,10 @@ void OMW::Engine::go() mEnvironment.getWorld()->updateWindowManager(); mViewer->renderingTraversals(); + + bool guiActive = mEnvironment.getWindowManager()->isGuiMode(); + if (!guiActive) + simulationTime += dt; } mEnvironment.limitFrameRate(frameTimer.time_s()); diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 3cbd4b6e7..e42a5c94f 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -119,7 +119,7 @@ namespace OMW void executeLocalScripts(); - void frame (float dt); + bool frame (float dt); /// Load settings from various files, returns the path to the user settings file std::string loadSettings (Settings::Manager & settings); diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index ac891b980..109610b1e 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -290,7 +290,11 @@ private: }; #endif +#ifdef ANDROID +extern "C" int SDL_main(int argc, char**argv) +#else int main(int argc, char**argv) +#endif { #if defined(__APPLE__) setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0); diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 398439ad8..e40bf56bb 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -140,7 +140,7 @@ namespace MWBase /// Utility to check if taking this item is illegal and calling commitCrime if so /// @param container The container the item is in; may be empty for an item in the world virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container, - int count) = 0; + int count, bool alarm = true) = 0; /// Utility to check if opening (i.e. unlocking) this object is illegal and calling commitCrime if so virtual void objectOpened (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item) = 0; /// Attempt sleeping in a bed. If this is illegal, call commitCrime. @@ -191,6 +191,9 @@ namespace MWBase virtual void getObjectsInRange (const osg::Vec3f& position, float radius, std::vector& objects) = 0; virtual void getActorsInRange(const osg::Vec3f &position, float radius, std::vector &objects) = 0; + /// Check if there are actors in selected range + virtual bool isAnyActorInRange(const osg::Vec3f &position, float radius) = 0; + ///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 **/ virtual std::list getActorsSidingWith(const MWWorld::Ptr& actor) = 0; @@ -238,6 +241,7 @@ namespace MWBase /// Has the player stolen this item from the given owner? virtual bool isItemStolenFrom(const std::string& itemid, const std::string& ownerid) = 0; + virtual bool isBoundItem(const MWWorld::Ptr& item) = 0; virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, MWWorld::Ptr& victim) = 0; /// Turn actor into werewolf or normal form. @@ -250,7 +254,6 @@ namespace MWBase virtual void cleanupSummonedCreature(const MWWorld::Ptr& caster, int creatureActorId) = 0; virtual void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count) = 0; - virtual bool isAttackPrepairing(const MWWorld::Ptr& ptr) = 0; virtual bool isRunning(const MWWorld::Ptr& ptr) = 0; virtual bool isSneaking(const MWWorld::Ptr& ptr) = 0; diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index e7085bfbc..134bbf943 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1129,6 +1129,15 @@ namespace MWClass else { ratings[i] = it->getClass().getEffectiveArmorRating(*it, ptr); + + // Take in account armor condition + const bool hasHealth = it->getClass().hasItemHealth(*it); + if (hasHealth) + { + int armorHealth = it->getClass().getItemHealth(*it); + int armorMaxHealth = it->getClass().getItemMaxHealth(*it); + ratings[i] *= (float(armorHealth) / armorMaxHealth); + } } } diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 88e65e535..de9ca83ca 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -77,6 +77,7 @@ namespace MWDialogue void DialogueManager::parseText (const std::string& text) { + updateActorKnownTopics(); std::vector hypertext = HyperTextParser::parseHyperText(text); for (std::vector::iterator tok = hypertext.begin(); tok != hypertext.end(); ++tok) @@ -145,18 +146,12 @@ namespace MWDialogue // TODO play sound } - // first topics update so that parseText knows the keywords to highlight - updateActorKnownTopics(); - - parseText (info->mResponse); - MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); callback->addResponse("", Interpreter::fixDefinesDialog(info->mResponse, interpreterContext)); executeScript (info->mResultScript, mActor); mLastTopic = it->mId; - // update topics again to accommodate changes resulting from executeScript - updateActorKnownTopics(); + parseText (info->mResponse); return true; } @@ -252,8 +247,6 @@ namespace MWDialogue const ESM::DialInfo* info = filter.search(dialogue, true); if (info) { - parseText (info->mResponse); - std::string title; if (dialogue.mType==ESM::Dialogue::Persuasion) { @@ -292,6 +285,8 @@ namespace MWDialogue executeScript (info->mResultScript, mActor); + parseText (info->mResponse); + mLastTopic = topic; } } @@ -391,7 +386,7 @@ namespace MWDialogue { Filter filter (mActor, mChoice, mTalkedTo); - if (dialogue->mType == ESM::Dialogue::Topic || dialogue->mType == ESM::Dialogue::Greeting) + if (dialogue->mType == ESM::Dialogue::Topic || dialogue->mType == ESM::Dialogue::Greeting) { if (const ESM::DialInfo *info = filter.search (*dialogue, true)) { @@ -405,15 +400,18 @@ namespace MWDialogue MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); callback->addResponse("", Interpreter::fixDefinesDialog(text, interpreterContext)); - // Make sure the returned DialInfo is from the Dialogue we supplied. If could also be from the Info refusal group, - // in which case it should not be added to the journal. - for (ESM::Dialogue::InfoContainer::const_iterator iter = dialogue->mInfo.begin(); - iter!=dialogue->mInfo.end(); ++iter) + if (dialogue->mType == ESM::Dialogue::Topic) { - if (iter->mId == info->mId) + // Make sure the returned DialInfo is from the Dialogue we supplied. If could also be from the Info refusal group, + // in which case it should not be added to the journal + for (ESM::Dialogue::InfoContainer::const_iterator iter = dialogue->mInfo.begin(); + iter!=dialogue->mInfo.end(); ++iter) { - MWBase::Environment::get().getJournal()->addTopic (Misc::StringUtils::lowerCase(mLastTopic), info->mId, mActor); - break; + if (iter->mId == info->mId) + { + MWBase::Environment::get().getJournal()->addTopic (Misc::StringUtils::lowerCase(mLastTopic), info->mId, mActor); + break; + } } } diff --git a/apps/openmw/mwdialogue/journalimp.cpp b/apps/openmw/mwdialogue/journalimp.cpp index b56db1ebd..b21951618 100644 --- a/apps/openmw/mwdialogue/journalimp.cpp +++ b/apps/openmw/mwdialogue/journalimp.cpp @@ -83,8 +83,10 @@ namespace MWDialogue if (i->mTopic == id && i->mInfoId == infoId) { if (getJournalIndex(id) < index) + { setJournalIndex(id, index); - MWBase::Environment::get().getWindowManager()->messageBox ("#{sJournalEntry}"); + MWBase::Environment::get().getWindowManager()->messageBox ("#{sJournalEntry}"); + } return; } diff --git a/apps/openmw/mwgui/birth.cpp b/apps/openmw/mwgui/birth.cpp index 92f29e3ef..c1867541b 100644 --- a/apps/openmw/mwgui/birth.cpp +++ b/apps/openmw/mwgui/birth.cpp @@ -8,7 +8,9 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" + #include "../mwworld/esmstore.hpp" +#include "../mwworld/player.hpp" #include "widgets.hpp" @@ -70,8 +72,14 @@ namespace MWGui updateBirths(); updateSpells(); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mBirthList); - } + // Show the current birthsign by default + const std::string &signId = + MWBase::Environment::get().getWorld()->getPlayer().getBirthSign(); + + if (!signId.empty()) + setBirthId(signId); + } void BirthDialog::setBirthId(const std::string &birthId) { diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index 137594076..5a9237cea 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -16,6 +16,7 @@ class BookPageImpl; static bool ucsSpace (int codePoint); static bool ucsLineBreak (int codePoint); +static bool ucsCarriageReturn (int codePoint); static bool ucsBreakingSpace (int codePoint); struct BookTypesetter::Style { virtual ~Style () {} }; @@ -280,7 +281,7 @@ struct TypesetBookImpl::Typesetter : BookTypesetter style.mActiveColour = fontColour; style.mNormalColour = fontColour; style.mInteractiveId = 0; - + return &style; } @@ -342,7 +343,7 @@ struct TypesetBookImpl::Typesetter : BookTypesetter writeImpl (static_cast (style), begin_, end_); } - + void lineBreak (float margin) { assert (margin == 0); //TODO: figure out proper behavior here... @@ -352,7 +353,7 @@ struct TypesetBookImpl::Typesetter : BookTypesetter mRun = NULL; mLine = NULL; } - + void sectionBreak (int margin) { add_partial_text(); @@ -1188,6 +1189,9 @@ public: { Utf8Stream::UnicodeChar code_point = stream.consume (); + if (ucsCarriageReturn (code_point)) + continue; + if (!ucsSpace (code_point)) glyphStream.emitGlyph (code_point); else @@ -1331,6 +1335,11 @@ static bool ucsLineBreak (int codePoint) return codePoint == '\n'; } +static bool ucsCarriageReturn (int codePoint) +{ + return codePoint == '\r'; +} + static bool ucsSpace (int codePoint) { switch (codePoint) diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp index 33daa0ad1..4d2a15c82 100644 --- a/apps/openmw/mwgui/class.cpp +++ b/apps/openmw/mwgui/class.cpp @@ -7,6 +7,9 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" + +#include "../mwmechanics/actorutil.hpp" + #include "../mwworld/esmstore.hpp" #include "tooltips.hpp" @@ -131,8 +134,16 @@ namespace MWGui updateClasses(); updateStats(); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mClassList); - } + // Show the current class by default + MWWorld::Ptr player = MWMechanics::getPlayer(); + + const std::string &classId = + player.get()->mBase->mClass; + + if (!classId.empty()) + setClassId(classId); + } void PickClassDialog::setClassId(const std::string &classId) { diff --git a/apps/openmw/mwgui/companionwindow.cpp b/apps/openmw/mwgui/companionwindow.cpp index ad71be7df..c6ef2890d 100644 --- a/apps/openmw/mwgui/companionwindow.cpp +++ b/apps/openmw/mwgui/companionwindow.cpp @@ -1,5 +1,7 @@ #include "companionwindow.hpp" +#include + #include #include "../mwbase/environment.hpp" @@ -129,7 +131,7 @@ void CompanionWindow::updateEncumbranceBar() return; float capacity = mPtr.getClass().getCapacity(mPtr); float encumbrance = mPtr.getClass().getEncumbrance(mPtr); - mEncumbranceBar->setValue(static_cast(encumbrance), static_cast(capacity)); + mEncumbranceBar->setValue(std::ceil(encumbrance), static_cast(capacity)); if (mModel && mModel->hasProfit(mPtr)) { diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 74ed4fff9..9222f1d02 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -8,12 +8,10 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/dialoguemanager.hpp" #include "../mwbase/mechanicsmanager.hpp" -#include "../mwmechanics/actorutil.hpp" #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" -#include "../mwmechanics/pickpocket.hpp" #include "../mwmechanics/creaturestats.hpp" #include "countdialog.hpp" @@ -33,7 +31,6 @@ namespace MWGui ContainerWindow::ContainerWindow(DragAndDrop* dragAndDrop) : WindowBase("openmw_container_window.layout") , mDragAndDrop(dragAndDrop) - , mPickpocketDetected(false) , mSortModel(NULL) , mModel(NULL) , mSelectedItem(-1) @@ -55,10 +52,9 @@ namespace MWGui void ContainerWindow::onItemSelected(int index) { - if (mDragAndDrop->mIsOnDragAndDrop) + if (mDragAndDrop->mIsOnDragAndDrop && mModel) { - if (mModel && mModel->allowedToInsertItems()) - dropItem(); + dropItem(); return; } @@ -100,39 +96,20 @@ namespace MWGui void ContainerWindow::dropItem() { - if (mPtr.getTypeName() == typeid(ESM::Container).name()) - { - // check container organic flag - MWWorld::LiveCellRef* ref = mPtr.get(); - if (ref->mBase->mFlags & ESM::Container::Organic) - { - MWBase::Environment::get().getWindowManager()-> - messageBox("#{sContentsMessage2}"); - return; - } + bool success = mModel->onDropItem(mDragAndDrop->mItem.mBase, mDragAndDrop->mDraggedCount); - // check that we don't exceed container capacity - MWWorld::Ptr item = mDragAndDrop->mItem.mBase; - float weight = item.getClass().getWeight(item) * mDragAndDrop->mDraggedCount; - if (mPtr.getClass().getCapacity(mPtr) < mPtr.getClass().getEncumbrance(mPtr) + weight) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sContentsMessage3}"); - return; - } - } - - mDragAndDrop->drop(mModel, mItemView); + if (success) + mDragAndDrop->drop(mModel, mItemView); } void ContainerWindow::onBackgroundSelected() { - if (mDragAndDrop->mIsOnDragAndDrop && mModel && mModel->allowedToInsertItems()) + if (mDragAndDrop->mIsOnDragAndDrop && mModel) dropItem(); } void ContainerWindow::setPtr(const MWWorld::Ptr& container) { - mPickpocketDetected = false; mPtr = container; bool loot = mPtr.getClass().isActor() && mPtr.getClass().getCreatureStats(mPtr).isDead(); @@ -142,8 +119,7 @@ namespace MWGui if (mPtr.getClass().isNpc() && !loot) { // we are stealing stuff - MWWorld::Ptr player = MWMechanics::getPlayer(); - mModel = new PickpocketItemModel(player, new InventoryItemModel(container), + mModel = new PickpocketItemModel(mPtr, new InventoryItemModel(container), !mPtr.getClass().getCreatureStats(mPtr).getKnockedDown()); } else @@ -178,24 +154,8 @@ namespace MWGui { WindowBase::onClose(); - if (dynamic_cast(mModel) - // Make sure we were actually closed, rather than just temporarily hidden (e.g. console or main menu opened) - && !MWBase::Environment::get().getWindowManager()->containsMode(GM_Container) - // If it was already detected while taking an item, no need to check now - && !mPickpocketDetected - ) - { - MWWorld::Ptr player = MWMechanics::getPlayer(); - MWMechanics::Pickpocket pickpocket(player, mPtr); - if (pickpocket.finish()) - { - MWBase::Environment::get().getMechanicsManager()->commitCrime( - player, mPtr, MWBase::MechanicsManager::OT_Pickpocket, 0, true); - MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Container); - mPickpocketDetected = true; - return; - } - } + if (mModel) + mModel->onClose(); } void ContainerWindow::onCloseButtonClicked(MyGUI::Widget* _sender) @@ -271,32 +231,7 @@ namespace MWGui bool ContainerWindow::onTakeItem(const ItemStack &item, int count) { - MWWorld::Ptr player = MWMechanics::getPlayer(); - // TODO: move to ItemModels - if (dynamic_cast(mModel) - && !mPtr.getClass().getCreatureStats(mPtr).getKnockedDown()) - { - MWMechanics::Pickpocket pickpocket(player, mPtr); - if (pickpocket.pick(item.mBase, count)) - { - MWBase::Environment::get().getMechanicsManager()->commitCrime( - player, mPtr, MWBase::MechanicsManager::OT_Pickpocket, 0, true); - MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Container); - mPickpocketDetected = true; - return false; - } - else - player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, 1); - } - else - { - // Looting a dead corpse is considered OK - if (mPtr.getClass().isActor() && mPtr.getClass().getCreatureStats(mPtr).isDead()) - return true; - else - MWBase::Environment::get().getMechanicsManager()->itemTaken(player, item.mBase, mPtr, count); - } - return true; + return mModel->onTakeItem(item.mBase, count); } } diff --git a/apps/openmw/mwgui/container.hpp b/apps/openmw/mwgui/container.hpp index 50c69da3b..cf02d165d 100644 --- a/apps/openmw/mwgui/container.hpp +++ b/apps/openmw/mwgui/container.hpp @@ -44,8 +44,6 @@ namespace MWGui private: DragAndDrop* mDragAndDrop; - bool mPickpocketDetected; - MWGui::ItemView* mItemView; SortFilterItemModel* mSortModel; ItemModel* mModel; diff --git a/apps/openmw/mwgui/containeritemmodel.cpp b/apps/openmw/mwgui/containeritemmodel.cpp index 9f7a9965c..24e7f8d00 100644 --- a/apps/openmw/mwgui/containeritemmodel.cpp +++ b/apps/openmw/mwgui/containeritemmodel.cpp @@ -2,12 +2,16 @@ #include +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/actorutil.hpp" + #include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" -#include "../mwbase/world.hpp" -#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwmechanics/actorutil.hpp" @@ -185,5 +189,51 @@ void ContainerItemModel::update() } } } +bool ContainerItemModel::onDropItem(const MWWorld::Ptr &item, int count) +{ + if (mItemSources.empty()) + return false; + + MWWorld::Ptr target = mItemSources[0]; + + if (target.getTypeName() != typeid(ESM::Container).name()) + return true; + + // check container organic flag + MWWorld::LiveCellRef* ref = target.get(); + if (ref->mBase->mFlags & ESM::Container::Organic) + { + MWBase::Environment::get().getWindowManager()-> + messageBox("#{sContentsMessage2}"); + return false; + } + + // check that we don't exceed container capacity + float weight = item.getClass().getWeight(item) * count; + if (target.getClass().getCapacity(target) < target.getClass().getEncumbrance(target) + weight) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sContentsMessage3}"); + return false; + } + + return true; +} + +bool ContainerItemModel::onTakeItem(const MWWorld::Ptr &item, int count) +{ + if (mItemSources.empty()) + return false; + + MWWorld::Ptr target = mItemSources[0]; + + // Looting a dead corpse is considered OK + if (target.getClass().isActor() && target.getClass().getCreatureStats(target).isDead()) + return true; + + MWWorld::Ptr player = MWMechanics::getPlayer(); + MWBase::Environment::get().getMechanicsManager()->itemTaken(player, item, target, count); + + return true; +} } diff --git a/apps/openmw/mwgui/containeritemmodel.hpp b/apps/openmw/mwgui/containeritemmodel.hpp index c6ecafd46..e8d1c5328 100644 --- a/apps/openmw/mwgui/containeritemmodel.hpp +++ b/apps/openmw/mwgui/containeritemmodel.hpp @@ -18,6 +18,10 @@ namespace MWGui ContainerItemModel (const MWWorld::Ptr& source); virtual bool allowedToUseItems() const; + + virtual bool onDropItem(const MWWorld::Ptr &item, int count); + virtual bool onTakeItem(const MWWorld::Ptr &item, int count); + virtual ItemStack getItem (ModelIndex index); virtual ModelIndex getIndex (ItemStack item); virtual size_t getItemCount(); diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 8a7ae85ea..14bbe81ef 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -45,6 +45,11 @@ namespace MWGui mWindow->addResponse(title, text, mNeedMargin); } + void updateTopics() + { + mWindow->updateTopics(); + } + private: DialogueWindow* mWindow; bool mNeedMargin; @@ -91,6 +96,7 @@ namespace MWGui type = MWBase::MechanicsManager::PT_Bribe1000; MWBase::Environment::get().getDialogueManager()->persuade(type, mCallback.get()); + mCallback->updateTopics(); setVisible(false); } @@ -395,6 +401,8 @@ namespace MWGui else if (topic == gmst.find("sRepair")->getString()) MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_MerchantRepair, mPtr); } + else + updateTopics(); } } @@ -432,7 +440,9 @@ namespace MWGui setTitle(mPtr.getClass().getName(mPtr)); - updateTopicsPane(); + updateTopics(); + updateTopicsPane(); // force update for new services + updateDisposition(); restock(); } @@ -620,11 +630,13 @@ namespace MWGui void DialogueWindow::onTopicActivated(const std::string &topicId) { MWBase::Environment::get().getDialogueManager()->keywordSelected(topicId, mCallback.get()); + updateTopics(); } void DialogueWindow::onChoiceActivated(int id) { MWBase::Environment::get().getDialogueManager()->questionAnswered(id, mCallback.get()); + updateTopics(); } void DialogueWindow::onGoodbyeActivated() diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp index 277032513..2538602c6 100644 --- a/apps/openmw/mwgui/dialogue.hpp +++ b/apps/openmw/mwgui/dialogue.hpp @@ -131,8 +131,9 @@ namespace MWGui void onFrame(float dt); void clear() { resetReference(); } - protected: void updateTopics(); + + protected: void updateTopicsPane(); bool isCompanion(const MWWorld::Ptr& actor); bool isCompanion(); diff --git a/apps/openmw/mwgui/inventoryitemmodel.cpp b/apps/openmw/mwgui/inventoryitemmodel.cpp index 222243ec1..c9f55d352 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.cpp +++ b/apps/openmw/mwgui/inventoryitemmodel.cpp @@ -9,6 +9,9 @@ #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" + namespace MWGui { @@ -116,4 +119,16 @@ void InventoryItemModel::update() } } +bool InventoryItemModel::onTakeItem(const MWWorld::Ptr &item, int count) +{ + // Looting a dead corpse is considered OK + if (mActor.getClass().isActor() && mActor.getClass().getCreatureStats(mActor).isDead()) + return true; + + MWWorld::Ptr player = MWMechanics::getPlayer(); + MWBase::Environment::get().getMechanicsManager()->itemTaken(player, item, mActor, count); + + return true; +} + } diff --git a/apps/openmw/mwgui/inventoryitemmodel.hpp b/apps/openmw/mwgui/inventoryitemmodel.hpp index f58ee2939..38730bd4d 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.hpp +++ b/apps/openmw/mwgui/inventoryitemmodel.hpp @@ -15,6 +15,8 @@ namespace MWGui virtual ModelIndex getIndex (ItemStack item); virtual size_t getItemCount(); + virtual bool onTakeItem(const MWWorld::Ptr &item, int count); + virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool setNewOwner=false); virtual void removeItem (const ItemStack& item, size_t count); diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index a92c77760..7bf8ad025 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -1,5 +1,6 @@ #include "inventorywindow.hpp" +#include #include #include @@ -598,7 +599,7 @@ namespace MWGui float capacity = player.getClass().getCapacity(player); float encumbrance = player.getClass().getEncumbrance(player); mTradeModel->adjustEncumbrance(encumbrance); - mEncumbranceBar->setValue(static_cast(encumbrance), static_cast(capacity)); + mEncumbranceBar->setValue(std::ceil(encumbrance), static_cast(capacity)); } void InventoryWindow::onFrame(float dt) @@ -657,12 +658,16 @@ namespace MWGui MWWorld::Ptr player = MWMechanics::getPlayer(); MWBase::Environment::get().getWorld()->breakInvisibility(player); + + if (!object.getRefData().activate()) + return; MWBase::Environment::get().getMechanicsManager()->itemTaken(player, object, MWWorld::Ptr(), count); // add to player inventory // can't use ActionTake here because we need an MWWorld::Ptr to the newly inserted object MWWorld::Ptr newObject = *player.getClass().getContainerStore (player).add (object, object.getRefData().getCount(), player); + // remove from world MWBase::Environment::get().getWorld()->deleteObject (object); diff --git a/apps/openmw/mwgui/itemmodel.cpp b/apps/openmw/mwgui/itemmodel.cpp index ffcf9075e..e3f38a54c 100644 --- a/apps/openmw/mwgui/itemmodel.cpp +++ b/apps/openmw/mwgui/itemmodel.cpp @@ -9,6 +9,7 @@ #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" namespace MWGui { @@ -23,38 +24,7 @@ namespace MWGui if (base.getClass().getEnchantment(base) != "") mFlags |= Flag_Enchanted; - static std::set boundItemIDCache; - - // If this is empty then we haven't executed the GMST cache logic yet; or there isn't any sMagicBound* GMST's for some reason - if (boundItemIDCache.empty()) - { - // Build a list of known bound item ID's - const MWWorld::Store &gameSettings = MWBase::Environment::get().getWorld()->getStore().get(); - - for (MWWorld::Store::iterator currentIteration = gameSettings.begin(); currentIteration != gameSettings.end(); ++currentIteration) - { - const ESM::GameSetting ¤tSetting = *currentIteration; - std::string currentGMSTID = currentSetting.mId; - Misc::StringUtils::lowerCaseInPlace(currentGMSTID); - - // Don't bother checking this GMST if it's not a sMagicBound* one. - const std::string& toFind = "smagicbound"; - if (currentGMSTID.compare(0, toFind.length(), toFind) != 0) - continue; - - // All sMagicBound* GMST's should be of type string - std::string currentGMSTValue = currentSetting.getString(); - Misc::StringUtils::lowerCaseInPlace(currentGMSTValue); - - boundItemIDCache.insert(currentGMSTValue); - } - } - - // Perform bound item check and assign the Flag_Bound bit if it passes - std::string tempItemID = base.getCellRef().getRefId(); - Misc::StringUtils::lowerCaseInPlace(tempItemID); - - if (boundItemIDCache.count(tempItemID) != 0) + if (MWBase::Environment::get().getMechanicsManager()->isBoundItem(base)) mFlags |= Flag_Bound; } @@ -124,7 +94,12 @@ namespace MWGui return true; } - bool ItemModel::allowedToInsertItems() const + bool ItemModel::onDropItem(const MWWorld::Ptr &item, int count) + { + return true; + } + + bool ItemModel::onTakeItem(const MWWorld::Ptr &item, int count) { return true; } @@ -198,4 +173,18 @@ namespace MWGui mSourceModel = sourceModel; } + void ProxyItemModel::onClose() + { + mSourceModel->onClose(); + } + + bool ProxyItemModel::onDropItem(const MWWorld::Ptr &item, int count) + { + return mSourceModel->onDropItem(item, count); + } + + bool ProxyItemModel::onTakeItem(const MWWorld::Ptr &item, int count) + { + return mSourceModel->onTakeItem(item, count); + } } diff --git a/apps/openmw/mwgui/itemmodel.hpp b/apps/openmw/mwgui/itemmodel.hpp index bc6be8023..e8e348a8a 100644 --- a/apps/openmw/mwgui/itemmodel.hpp +++ b/apps/openmw/mwgui/itemmodel.hpp @@ -72,9 +72,11 @@ namespace MWGui /// Is the player allowed to use items from this item model? (default true) virtual bool allowedToUseItems() const; - - /// Is the player allowed to insert items into this model? (default true) - virtual bool allowedToInsertItems() const; + virtual void onClose() + { + } + virtual bool onDropItem(const MWWorld::Ptr &item, int count); + virtual bool onTakeItem(const MWWorld::Ptr &item, int count); private: ItemModel(const ItemModel&); @@ -91,6 +93,10 @@ namespace MWGui bool allowedToUseItems() const; + virtual void onClose(); + virtual bool onDropItem(const MWWorld::Ptr &item, int count); + virtual bool onTakeItem(const MWWorld::Ptr &item, int count); + virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool setNewOwner=false); virtual void removeItem (const ItemStack& item, size_t count); virtual ModelIndex getIndex (ItemStack item); diff --git a/apps/openmw/mwgui/journalbooks.cpp b/apps/openmw/mwgui/journalbooks.cpp index a1d74fab6..e8aa23158 100644 --- a/apps/openmw/mwgui/journalbooks.cpp +++ b/apps/openmw/mwgui/journalbooks.cpp @@ -5,8 +5,10 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" -#include "textcolours.hpp" +#include +#include +#include "textcolours.hpp" namespace { @@ -154,8 +156,8 @@ MWGui::BookTypesetter::Utf8Span to_utf8_span (char const * text) typedef TypesetBook::Ptr book; -JournalBooks::JournalBooks (JournalViewModel::Ptr model) : - mModel (model) +JournalBooks::JournalBooks (JournalViewModel::Ptr model, ToUTF8::FromType encoding) : + mModel (model), mEncoding(encoding) { } @@ -217,34 +219,82 @@ book JournalBooks::createQuestBook (const std::string& questName) } book JournalBooks::createTopicIndexBook () +{ + bool isRussian = (mEncoding == ToUTF8::WINDOWS_1251); + + BookTypesetter::Ptr typesetter = isRussian ? createCyrillicJournalIndex() : createLatinJournalIndex(); + + return typesetter->complete (); +} + +BookTypesetter::Ptr JournalBooks::createLatinJournalIndex () { BookTypesetter::Ptr typesetter = BookTypesetter::create (92, 250); typesetter->setSectionAlignment (BookTypesetter::AlignCenter); - BookTypesetter::Style* body = typesetter->createStyle ("", MyGUI::Colour::Black); + char ch = 'A'; + BookTypesetter::Style* body = typesetter->createStyle ("", MyGUI::Colour::Black); for (int i = 0; i < 26; ++i) { - char ch = 'A' + i; - char buffer [32]; - sprintf (buffer, "( %c )", ch); const MWGui::TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours(); BookTypesetter::Style* style = typesetter->createHotStyle (body, textColours.journalTopic, textColours.journalTopicOver, - textColours.journalTopicPressed, ch); + textColours.journalTopicPressed, (uint32_t) ch); if (i == 13) typesetter->sectionBreak (); typesetter->write (style, to_utf8_span (buffer)); typesetter->lineBreak (); + + ch++; } - return typesetter->complete (); + return typesetter; +} + +BookTypesetter::Ptr JournalBooks::createCyrillicJournalIndex () +{ + BookTypesetter::Ptr typesetter = BookTypesetter::create (92, 250); + + typesetter->setSectionAlignment (BookTypesetter::AlignCenter); + + BookTypesetter::Style* body = typesetter->createStyle ("", MyGUI::Colour::Black); + + unsigned char ch[2] = {0xd0, 0x90}; // CYRILLIC CAPITAL A is a 0xd090 in UTF-8 + + for (int i = 0; i < 32; ++i) + { + char buffer [32]; + sprintf(buffer, "( %c%c )", ch[0], ch[1]); + + Utf8Stream stream ((char*) ch); + uint32_t first = stream.peek(); + + const MWGui::TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours(); + BookTypesetter::Style* style = typesetter->createHotStyle (body, textColours.journalTopic, + textColours.journalTopicOver, + textColours.journalTopicPressed, first); + + ch[1]++; + + // Words can not be started with these characters + if (i == 26 || i == 28) + continue; + + if (i == 15) + typesetter->sectionBreak (); + + typesetter->write (style, to_utf8_span (buffer)); + typesetter->lineBreak (); + } + + return typesetter; } BookTypesetter::Ptr JournalBooks::createTypesetter () diff --git a/apps/openmw/mwgui/journalbooks.hpp b/apps/openmw/mwgui/journalbooks.hpp index 8f87825f0..aa36eecdf 100644 --- a/apps/openmw/mwgui/journalbooks.hpp +++ b/apps/openmw/mwgui/journalbooks.hpp @@ -4,6 +4,8 @@ #include "bookpage.hpp" #include "journalviewmodel.hpp" +#include + namespace MWGui { MWGui::BookTypesetter::Utf8Span to_utf8_span (char const * text); @@ -13,7 +15,7 @@ namespace MWGui typedef TypesetBook::Ptr Book; JournalViewModel::Ptr mModel; - JournalBooks (JournalViewModel::Ptr model); + JournalBooks (JournalViewModel::Ptr model, ToUTF8::FromType encoding); Book createEmptyJournalBook (); Book createJournalBook (); @@ -22,8 +24,12 @@ namespace MWGui Book createQuestBook (const std::string& questName); Book createTopicIndexBook (); + ToUTF8::FromType mEncoding; + private: BookTypesetter::Ptr createTypesetter (); + BookTypesetter::Ptr createLatinJournalIndex (); + BookTypesetter::Ptr createCyrillicJournalIndex (); }; } diff --git a/apps/openmw/mwgui/journalviewmodel.cpp b/apps/openmw/mwgui/journalviewmodel.cpp index b5d08eec6..6ff68c9c5 100644 --- a/apps/openmw/mwgui/journalviewmodel.cpp +++ b/apps/openmw/mwgui/journalviewmodel.cpp @@ -6,6 +6,8 @@ #include #include +#include +#include #include "../mwbase/world.hpp" #include "../mwbase/journal.hpp" @@ -305,18 +307,37 @@ struct JournalViewModelImpl : JournalViewModel visitor (toUtf8Span (topic.getName())); } - void visitTopicNamesStartingWith (char character, std::function < void (const std::string&) > visitor) const + void visitTopicNamesStartingWith (uint32_t character, std::function < void (const std::string&) > visitor) const { MWBase::Journal * journal = MWBase::Environment::get().getJournal(); for (MWBase::Journal::TTopicIter i = journal->topicBegin (); i != journal->topicEnd (); ++i) { - if (i->first [0] != Misc::StringUtils::toLower(character)) + Utf8Stream stream (i->first.c_str()); + uint32_t first = toUpper(stream.peek()); + + if (first != character) continue; visitor (i->second.getName()); } + } + + static uint32_t toUpper(uint32_t ch) + { + // Russian alphabet + if (ch >= 0x0430 && ch < 0x0450) + ch -= 0x20; + + // Cyrillic IO character + if (ch == 0x0451) + ch -= 0x50; + + // Latin alphabet + if (ch >= 0x61 && ch < 0x80) + ch -= 0x20; + return ch; } struct TopicEntryImpl : BaseEntry diff --git a/apps/openmw/mwgui/journalviewmodel.hpp b/apps/openmw/mwgui/journalviewmodel.hpp index 3edde3d31..01dcb49de 100644 --- a/apps/openmw/mwgui/journalviewmodel.hpp +++ b/apps/openmw/mwgui/journalviewmodel.hpp @@ -75,8 +75,8 @@ namespace MWGui /// provides the name of the topic specified by its id virtual void visitTopicName (TopicId topicId, std::function visitor) const = 0; - /// walks over the topics whose names start with the specified character providing the topics name - virtual void visitTopicNamesStartingWith (char character, std::function < void (const std::string&) > visitor) const = 0; + /// walks over the topics whose names start with the character + virtual void visitTopicNamesStartingWith (uint32_t character, std::function < void (const std::string&) > visitor) const = 0; /// walks over the topic entries for the topic specified by its identifier virtual void visitTopicEntries (TopicId topicId, std::function visitor) const = 0; diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp index 30a440b58..4b7a789e8 100644 --- a/apps/openmw/mwgui/journalwindow.cpp +++ b/apps/openmw/mwgui/journalwindow.cpp @@ -100,8 +100,8 @@ namespace return getWidget (name); } - JournalWindowImpl (MWGui::JournalViewModel::Ptr Model, bool questList) - : JournalBooks (Model), JournalWindow() + JournalWindowImpl (MWGui::JournalViewModel::Ptr Model, bool questList, ToUTF8::FromType encoding) + : JournalBooks (Model, encoding), JournalWindow() { center(); @@ -161,6 +161,15 @@ namespace Gui::ImageButton* showActiveButton = getWidget(ShowActiveBTN); Gui::ImageButton* showAllButton = getWidget(ShowAllBTN); Gui::ImageButton* questsButton = getWidget(QuestsBTN); + + Gui::ImageButton* nextButton = getWidget(NextPageBTN); + if (nextButton->getSize().width == 64) + { + // english button has a 7 pixel wide strip of garbage on its right edge + nextButton->setSize(64-7, nextButton->getSize().height); + nextButton->setImageCoord(MyGUI::IntCoord(0,0,64-7,nextButton->getSize().height)); + } + if (!questList) { // If tribunal is not installed (-> no options button), we still want the Topics button available, @@ -176,6 +185,8 @@ namespace showActiveButton->setVisible(false); showAllButton->setVisible(false); questsButton->setVisible(false); + + adjustButton(TopicsBTN); } else { @@ -188,23 +199,25 @@ namespace adjustButton(ShowActiveBTN); adjustButton(OptionsBTN); adjustButton(QuestsBTN); - } - - Gui::ImageButton* nextButton = getWidget(NextPageBTN); - if (nextButton->getSize().width == 64) - { - // english button has a 7 pixel wide strip of garbage on its right edge - nextButton->setSize(64-7, nextButton->getSize().height); - nextButton->setImageCoord(MyGUI::IntCoord(0,0,64-7,nextButton->getSize().height)); - } + adjustButton(TopicsBTN); + int topicsWidth = getWidget(TopicsBTN)->getSize().width; + int cancelLeft = getWidget(CancelBTN)->getPosition().left; + int cancelRight = getWidget(CancelBTN)->getPosition().left + getWidget(CancelBTN)->getSize().width; - adjustButton(TopicsBTN); - int topicsWidth = getWidget(TopicsBTN)->getSize().width; - int cancelLeft = getWidget(CancelBTN)->getPosition().left; - int cancelRight = getWidget(CancelBTN)->getPosition().left + getWidget(CancelBTN)->getSize().width; + getWidget(QuestsBTN)->setPosition(cancelRight, getWidget(QuestsBTN)->getPosition().top); - getWidget(TopicsBTN)->setPosition(cancelLeft - topicsWidth, getWidget(TopicsBTN)->getPosition().top); - getWidget(QuestsBTN)->setPosition(cancelRight, getWidget(QuestsBTN)->getPosition().top); + // Usually Topics, Quests, and Cancel buttons have the 64px width, so we can place the Topics left-up from the Cancel button, and the Quests right-up from the Cancel button. + // But in some installations, e.g. German one, the Topics button has the 128px width, so we should place it exactly left from the Quests button. + if (topicsWidth == 64) + { + getWidget(TopicsBTN)->setPosition(cancelLeft - topicsWidth, getWidget(TopicsBTN)->getPosition().top); + } + else + { + int questLeft = getWidget(QuestsBTN)->getPosition().left; + getWidget(TopicsBTN)->setPosition(questLeft - topicsWidth, getWidget(TopicsBTN)->getPosition().top); + } + } mQuestMode = false; mAllQuests = false; @@ -464,7 +477,7 @@ namespace MWBase::Environment::get().getWindowManager()->playSound("book page"); } - void notifyIndexLinkClicked (MWGui::TypesetBook::InteractiveId character) + void notifyIndexLinkClicked (MWGui::TypesetBook::InteractiveId index) { setVisible (LeftTopicIndex, false); setVisible (RightTopicIndex, false); @@ -477,7 +490,7 @@ namespace AddNamesToList add(list); - mModel->visitTopicNamesStartingWith((char) character, add); + mModel->visitTopicNamesStartingWith(index, add); list->adjustSize(); @@ -632,9 +645,9 @@ namespace } // glue the implementation to the interface -MWGui::JournalWindow * MWGui::JournalWindow::create (JournalViewModel::Ptr Model, bool questList) +MWGui::JournalWindow * MWGui::JournalWindow::create (JournalViewModel::Ptr Model, bool questList, ToUTF8::FromType encoding) { - return new JournalWindowImpl (Model, questList); + return new JournalWindowImpl (Model, questList, encoding); } MWGui::JournalWindow::JournalWindow() diff --git a/apps/openmw/mwgui/journalwindow.hpp b/apps/openmw/mwgui/journalwindow.hpp index c9bf0ef0a..62080f72e 100644 --- a/apps/openmw/mwgui/journalwindow.hpp +++ b/apps/openmw/mwgui/journalwindow.hpp @@ -3,6 +3,8 @@ #include "windowbase.hpp" +#include + #include namespace MWBase { class WindowManager; } @@ -16,7 +18,7 @@ namespace MWGui JournalWindow(); /// construct a new instance of the one JournalWindow implementation - static JournalWindow * create (std::shared_ptr Model, bool questList); + static JournalWindow * create (std::shared_ptr Model, bool questList, ToUTF8::FromType encoding); /// destroy this instance of the JournalWindow implementation virtual ~JournalWindow () {}; diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 023a693c9..99fe777aa 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -163,7 +163,6 @@ namespace MWGui if (mViewer->getIncrementalCompileOperation()) { mViewer->getIncrementalCompileOperation()->setMaximumNumOfObjectsToCompilePerFrame(100); - mViewer->getIncrementalCompileOperation()->setTargetFrameRate(getTargetFrameRate()); } // Assign dummy bounding sphere callback to avoid the bounding sphere of the entire scene being recomputed after each frame of loading diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index 7c89c4979..a79112b9f 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -207,8 +207,6 @@ namespace MWGui , mMessageBoxManager(parMessageBoxManager) , mButtonPressed(-1) { - setVisible(true); - int textPadding = 10; // padding between text-widget and main-widget int textButtonPadding = 10; // padding between the text-widget und the button-widget int buttonLeftPadding = 10; // padding between the buttons if horizontal @@ -358,7 +356,11 @@ namespace MWGui mMessageWidget->setCoord(messageWidgetCoord); } - // Set key focus to "Ok" button + setVisible(true); + } + + MyGUI::Widget* InteractiveMessageBox::getDefaultKeyFocus() + { std::vector keywords { "sOk", "sYes" }; for(std::vector::const_iterator button = mButtons.begin(); button != mButtons.end(); ++button) { @@ -366,11 +368,11 @@ namespace MWGui { if(Misc::StringUtils::ciEqual(MyGUI::LanguageManager::getInstance().replaceTags("#{" + keyword + "}"), (*button)->getCaption())) { - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(*button); - return; + return *button; } } } + return NULL; } void InteractiveMessageBox::mousePressed (MyGUI::Widget* pressed) diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index 53b277416..e0bcbe667 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -28,6 +28,8 @@ namespace MWGui bool createInteractiveMessageBox (const std::string& message, const std::vector& buttons); bool isInteractiveMessageBox (); + const InteractiveMessageBox* getInteractiveMessageBox() const { return mInterMessageBoxe; } + /// Remove all message boxes void clear(); @@ -77,6 +79,8 @@ namespace MWGui void mousePressed (MyGUI::Widget* _widget); int readPressedButton (); + MyGUI::Widget* getDefaultKeyFocus() override; + virtual bool exit() { return false; } bool mMarkedToDelete; diff --git a/apps/openmw/mwgui/pickpocketitemmodel.cpp b/apps/openmw/mwgui/pickpocketitemmodel.cpp index ec4b378a0..e85715e87 100644 --- a/apps/openmw/mwgui/pickpocketitemmodel.cpp +++ b/apps/openmw/mwgui/pickpocketitemmodel.cpp @@ -3,15 +3,26 @@ #include #include +#include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/pickpocket.hpp" + +#include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/windowmanager.hpp" + namespace MWGui { - PickpocketItemModel::PickpocketItemModel(const MWWorld::Ptr& thief, ItemModel *sourceModel, bool hideItems) + PickpocketItemModel::PickpocketItemModel(const MWWorld::Ptr& actor, ItemModel *sourceModel, bool hideItems) + : mActor(actor), mPickpocketDetected(false) { + MWWorld::Ptr player = MWMechanics::getPlayer(); mSourceModel = sourceModel; - int chance = thief.getClass().getSkill(thief, ESM::Skill::Sneak); + int chance = player.getClass().getSkill(player, ESM::Skill::Sneak); mSourceModel->update(); @@ -66,13 +77,63 @@ namespace MWGui void PickpocketItemModel::removeItem (const ItemStack &item, size_t count) { ProxyItemModel::removeItem(item, count); - /// \todo check if player is detected } - bool PickpocketItemModel::allowedToInsertItems() const + bool PickpocketItemModel::onDropItem(const MWWorld::Ptr &item, int count) { - // don't allow "reverse pickpocket" (yet) + // don't allow "reverse pickpocket" (it will be handled by scripts after 1.0) return false; } + void PickpocketItemModel::onClose() + { + // Make sure we were actually closed, rather than just temporarily hidden (e.g. console or main menu opened) + if (MWBase::Environment::get().getWindowManager()->containsMode(GM_Container) + // If it was already detected while taking an item, no need to check now + || mPickpocketDetected) + return; + + MWWorld::Ptr player = MWMechanics::getPlayer(); + MWMechanics::Pickpocket pickpocket(player, mActor); + if (pickpocket.finish()) + { + MWBase::Environment::get().getMechanicsManager()->commitCrime( + player, mActor, MWBase::MechanicsManager::OT_Pickpocket, 0, true); + MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Container); + mPickpocketDetected = true; + } + } + + bool PickpocketItemModel::onTakeItem(const MWWorld::Ptr &item, int count) + { + if (mActor.getClass().getCreatureStats(mActor).getKnockedDown()) + return mSourceModel->onTakeItem(item, count); + + bool success = stealItem(item, count); + if (success) + { + MWWorld::Ptr player = MWMechanics::getPlayer(); + MWBase::Environment::get().getMechanicsManager()->itemTaken(player, item, mActor, count, false); + } + + return success; + } + + bool PickpocketItemModel::stealItem(const MWWorld::Ptr &item, int count) + { + MWWorld::Ptr player = MWMechanics::getPlayer(); + MWMechanics::Pickpocket pickpocket(player, mActor); + if (pickpocket.pick(item, count)) + { + MWBase::Environment::get().getMechanicsManager()->commitCrime( + player, mActor, MWBase::MechanicsManager::OT_Pickpocket, 0, true); + MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Container); + mPickpocketDetected = true; + return false; + } + else + player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, 1); + + return true; + } } diff --git a/apps/openmw/mwgui/pickpocketitemmodel.hpp b/apps/openmw/mwgui/pickpocketitemmodel.hpp index 1b08ec485..83caf2ffd 100644 --- a/apps/openmw/mwgui/pickpocketitemmodel.hpp +++ b/apps/openmw/mwgui/pickpocketitemmodel.hpp @@ -17,7 +17,14 @@ namespace MWGui virtual size_t getItemCount(); virtual void update(); virtual void removeItem (const ItemStack& item, size_t count); - virtual bool allowedToInsertItems() const; + virtual void onClose(); + virtual bool onDropItem(const MWWorld::Ptr &item, int count); + virtual bool onTakeItem(const MWWorld::Ptr &item, int count); + + protected: + MWWorld::Ptr mActor; + bool mPickpocketDetected; + bool stealItem(const MWWorld::Ptr &item, int count); private: std::vector mHiddenItems; diff --git a/apps/openmw/mwgui/sortfilteritemmodel.cpp b/apps/openmw/mwgui/sortfilteritemmodel.cpp index e294ebe07..1ee3cf631 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.cpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.cpp @@ -311,4 +311,18 @@ namespace MWGui std::sort(mItems.begin(), mItems.end(), cmp); } + void SortFilterItemModel::onClose() + { + mSourceModel->onClose(); + } + + bool SortFilterItemModel::onDropItem(const MWWorld::Ptr &item, int count) + { + return mSourceModel->onDropItem(item, count); + } + + bool SortFilterItemModel::onTakeItem(const MWWorld::Ptr &item, int count) + { + return mSourceModel->onTakeItem(item, count); + } } diff --git a/apps/openmw/mwgui/sortfilteritemmodel.hpp b/apps/openmw/mwgui/sortfilteritemmodel.hpp index 6ddb019b0..98da8d8c9 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.hpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.hpp @@ -29,6 +29,10 @@ namespace MWGui /// Use ItemStack::Type for sorting? void setSortByType(bool sort) { mSortByType = sort; } + void onClose(); + bool onDropItem(const MWWorld::Ptr &item, int count); + bool onTakeItem(const MWWorld::Ptr &item, int count); + static const int Category_Weapon = (1<<1); static const int Category_Apparel = (1<<2); static const int Category_Misc = (1<<3); diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp index 6953d682b..f83b72096 100644 --- a/apps/openmw/mwgui/spellmodel.cpp +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -60,7 +60,7 @@ namespace MWGui { newSpell.mType = Spell::Type_Spell; std::string cost = std::to_string(spell->mData.mCost); - std::string chance = std::to_string(int(MWMechanics::getSpellSuccessChance(spell, mActor))); + std::string chance = std::to_string(int(MWMechanics::getSpellSuccessChance(spell, mActor, NULL, true, true))); newSpell.mCostColumn = cost + "/" + chance; } else diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index a7ad687cb..61febf315 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -96,6 +96,12 @@ namespace MWGui return (!mTimeAdvancer.isRunning()); //Only exit if not currently waiting } + void WaitDialog::clear() + { + mSleeping = false; + mTimeAdvancer.stop(); + } + void WaitDialog::onOpen() { if (mTimeAdvancer.isRunning()) diff --git a/apps/openmw/mwgui/waitdialog.hpp b/apps/openmw/mwgui/waitdialog.hpp index eb6a55640..2aecb002f 100644 --- a/apps/openmw/mwgui/waitdialog.hpp +++ b/apps/openmw/mwgui/waitdialog.hpp @@ -33,6 +33,8 @@ namespace MWGui virtual bool exit(); + virtual void clear(); + void onFrame(float dt); bool getSleeping() { return mTimeAdvancer.isRunning() && mSleeping; } diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index ccdd6916c..300f8e39f 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -193,6 +193,7 @@ namespace MWGui , mRestAllowed(true) , mFallbackMap(fallbackMap) , mShowOwned(0) + , mEncoding(encoding) , mVersionDescription(versionDescription) { float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); @@ -346,7 +347,7 @@ namespace MWGui mGuiModeStates[GM_Console] = GuiModeState(mConsole); bool questList = mResourceSystem->getVFS()->exists("textures/tx_menubook_options_over.dds"); - JournalWindow* journal = JournalWindow::create(JournalViewModel::create (), questList); + JournalWindow* journal = JournalWindow::create(JournalViewModel::create (), questList, mEncoding); mWindows.push_back(journal); mGuiModeStates[GM_Journal] = GuiModeState(journal); mGuiModeStates[GM_Journal].mCloseSound = "book close"; @@ -530,6 +531,7 @@ namespace MWGui delete mDragAndDrop; delete mSoulgemDialog; delete mCursorManager; + delete mToolTips; cleanupGarbage(); @@ -872,6 +874,22 @@ namespace MWGui window->onFrame(frameDuration); } + // Make sure message boxes are always in front + // This is an awful workaround for a series of awfully interwoven issues that couldn't be worked around + // in a better way because of an impressive number of even more awfully interwoven issues. + if (mMessageBoxManager && mMessageBoxManager->isInteractiveMessageBox() && mCurrentModals.back() != mMessageBoxManager->getInteractiveMessageBox()) + { + std::vector::iterator found = std::find(mCurrentModals.begin(), mCurrentModals.end(), mMessageBoxManager->getInteractiveMessageBox()); + if (found != mCurrentModals.end()) + { + WindowModal* msgbox = *found; + std::swap(*found, mCurrentModals.back()); + MyGUI::InputManager::getInstance().addWidgetModal(msgbox->mMainWidget); + mKeyboardNavigation->setModalWindow(msgbox->mMainWidget); + mKeyboardNavigation->setDefaultFocus(msgbox->mMainWidget, msgbox->getDefaultKeyFocus()); + } + } + if (!mCurrentModals.empty()) mCurrentModals.back()->onFrame(frameDuration); @@ -2068,5 +2086,4 @@ namespace MWGui for (unsigned int i=0; isetVisible(visible); } - } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index c8bc8d21e..1d250f6d4 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -511,6 +511,8 @@ namespace MWGui int mShowOwned; + ToUTF8::FromType mEncoding; + std::string mVersionDescription; MWGui::TextColours mTextColours; diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index e01deb669..2a101e58f 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -176,8 +176,25 @@ namespace MWInput } } + bool isLeftOrRightButton(int action, ICS::InputControlSystem* ics, int deviceId, bool joystick) + { + int mouseBinding = ics->getMouseButtonBinding(ics->getControl(action), ICS::Control::INCREASE); + if (mouseBinding != ICS_MAX_DEVICE_BUTTONS) + return true; + int buttonBinding = ics->getJoystickButtonBinding(ics->getControl(action), deviceId, ICS::Control::INCREASE); + if (joystick && (buttonBinding == 0 || buttonBinding == 1)) + return true; + return false; + } + void InputManager::handleGuiArrowKey(int action) { + if (SDL_IsTextInputActive()) + return; + + if (isLeftOrRightButton(action, mInputBinder, mFakeDeviceID, mJoystickLastUsed)) + return; + MyGUI::KeyCode key; switch (action) { @@ -232,7 +249,10 @@ namespace MWInput if (mControlSwitch["playercontrols"]) { if (action == A_Use) - mPlayer->setAttackingOrSpell(currentValue != 0); + { + MWMechanics::DrawState_ state = MWBase::Environment::get().getWorld()->getPlayer().getDrawState(); + mPlayer->setAttackingOrSpell(currentValue != 0 && state != MWMechanics::DrawState_Nothing); + } else if (action == A_Jump) mAttemptJump = (currentValue == 1.0 && previousValue == 0.0); } @@ -1143,7 +1163,10 @@ namespace MWInput void InputManager::activate() { if (MWBase::Environment::get().getWindowManager()->isGuiMode()) - MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::Return, 0); + { + if (!SDL_IsTextInputActive() && !isLeftOrRightButton(A_Activate, mInputBinder, mFakeDeviceID, mJoystickLastUsed)) + MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::Return, 0); + } else if (mControlSwitch["playercontrols"]) mPlayer->activate(); } @@ -1268,13 +1291,13 @@ namespace MWInput clearAllKeyBindings(control); if (defaultKeyBindings.find(i) != defaultKeyBindings.end() - && !mInputBinder->isKeyBound(defaultKeyBindings[i])) + && (force || !mInputBinder->isKeyBound(defaultKeyBindings[i]))) { control->setInitialValue(0.0f); mInputBinder->addKeyBinding(control, defaultKeyBindings[i], ICS::Control::INCREASE); } else if (defaultMouseButtonBindings.find(i) != defaultMouseButtonBindings.end() - && !mInputBinder->isMouseButtonBound(defaultMouseButtonBindings[i])) + && (force || !mInputBinder->isMouseButtonBound(defaultMouseButtonBindings[i]))) { control->setInitialValue(0.0f); mInputBinder->addMouseButtonBinding (control, defaultMouseButtonBindings[i], ICS::Control::INCREASE); @@ -1348,12 +1371,12 @@ namespace MWInput clearAllControllerBindings(control); if (defaultButtonBindings.find(i) != defaultButtonBindings.end() - && !mInputBinder->isJoystickButtonBound(mFakeDeviceID, defaultButtonBindings[i])) + && (force || !mInputBinder->isJoystickButtonBound(mFakeDeviceID, defaultButtonBindings[i]))) { control->setInitialValue(0.0f); mInputBinder->addJoystickButtonBinding(control, mFakeDeviceID, defaultButtonBindings[i], ICS::Control::INCREASE); } - else if (defaultAxisBindings.find(i) != defaultAxisBindings.end() && !mInputBinder->isJoystickAxisBound(mFakeDeviceID, defaultAxisBindings[i])) + else if (defaultAxisBindings.find(i) != defaultAxisBindings.end() && (force || !mInputBinder->isJoystickAxisBound(mFakeDeviceID, defaultAxisBindings[i]))) { control->setValue(0.5f); control->setInitialValue(0.5f); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index a144911c5..7a1e7270d 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1009,7 +1009,8 @@ namespace MWMechanics if (player.getClass().getNpcStats(player).isWerewolf()) return; - if (ptr.getClass().isClass(ptr, "Guard") && creatureStats.getAiSequence().getTypeId() != AiPackage::TypeIdPursue && !creatureStats.getAiSequence().isInCombat()) + if (ptr.getClass().isClass(ptr, "Guard") && creatureStats.getAiSequence().getTypeId() != AiPackage::TypeIdPursue && !creatureStats.getAiSequence().isInCombat() + && creatureStats.getMagicEffects().get(ESM::MagicEffect::CalmHumanoid).getMagnitude() == 0) { const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); static const int cutoff = esmStore.get().find("iCrimeThreshold")->getInt(); @@ -1148,6 +1149,46 @@ namespace MWMechanics } } + void Actors::updateCombatMusic () + { + MWWorld::Ptr player = getPlayer(); + int hostilesCount = 0; // need to know this to play Battle music + + for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) + { + if (!iter->first.getClass().getCreatureStats(iter->first).isDead()) + { + bool inProcessingRange = (player.getRefData().getPosition().asVec3() - iter->first.getRefData().getPosition().asVec3()).length2() + <= sqrAiProcessingDistance; + + if (MWBase::Environment::get().getMechanicsManager()->isAIActive() && inProcessingRange) + { + if (iter->first != player) + { + MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first); + if (stats.getAiSequence().isInCombat() && !stats.isDead()) hostilesCount++; + } + } + } + } + + // check if we still have any player enemies to switch music + static int currentMusic = 0; + + if (currentMusic != 1 && hostilesCount == 0 && !(player.getClass().getCreatureStats(player).isDead() && + MWBase::Environment::get().getSoundManager()->isMusicPlaying())) + { + MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); + currentMusic = 1; + } + else if (currentMusic != 2 && hostilesCount > 0) + { + MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Battle")); + currentMusic = 2; + } + + } + void Actors::update (float duration, bool paused) { if(!paused) @@ -1165,8 +1206,6 @@ namespace MWMechanics MWWorld::Ptr player = getPlayer(); - int hostilesCount = 0; // need to know this to play Battle music - /// \todo move update logic to Actor class where appropriate std::map > cachedAllies; // will be filled as engageCombat iterates @@ -1174,14 +1213,12 @@ namespace MWMechanics // AI and magic effects update for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { + float distSqr = (player.getRefData().getPosition().asVec3() - iter->first.getRefData().getPosition().asVec3()).length2(); // AI processing is only done within distance of 7168 units to the player. Note the "AI distance" slider doesn't affect this // (it only does some throttling for targets beyond the "AI distance", so doesn't give any guarantees as to whether AI will be enabled or not) // This distance could be made configurable later, but the setting must be marked with a big warning: // using higher values will make a quest in Bloodmoon harder or impossible to complete (bug #1876) - bool inProcessingRange = (player.getRefData().getPosition().asVec3() - iter->first.getRefData().getPosition().asVec3()).length2() - <= sqrAiProcessingDistance; - - iter->second->getCharacterController()->setActive(inProcessingRange); + bool inProcessingRange = distSqr <= sqrAiProcessingDistance; if (iter->first == player) iter->second->getCharacterController()->setAttackingOrSpell(MWBase::Environment::get().getWorld()->getPlayer().getAttackingOrSpell()); @@ -1230,8 +1267,13 @@ namespace MWMechanics float sqrHeadTrackDistance = std::numeric_limits::max(); MWWorld::Ptr headTrackTarget; + MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first); + // Unconsious actor can not track target - if (!iter->first.getClass().getCreatureStats(iter->first).getKnockedDown()) + // Also actors in combat and pursue mode do not bother to headtrack + if (!stats.getKnockedDown() && + !stats.getAiSequence().isInCombat() && + !stats.getAiSequence().hasPackage(AiPackage::TypeIdPursue)) { for(PtrActorMap::iterator it(mActors.begin()); it != mActors.end(); ++it) { @@ -1239,8 +1281,9 @@ namespace MWMechanics continue; updateHeadTracking(iter->first, it->first, headTrackTarget, sqrHeadTrackDistance); } - iter->second->getCharacterController()->setHeadTrackTarget(headTrackTarget); } + + iter->second->getCharacterController()->setHeadTrackTarget(headTrackTarget); } if (iter->first.getClass().isNpc() && iter->first != player) @@ -1251,8 +1294,6 @@ namespace MWMechanics CreatureStats &stats = iter->first.getClass().getCreatureStats(iter->first); if (isConscious(iter->first)) stats.getAiSequence().execute(iter->first, *iter->second->getCharacterController(), iter->second->getAiState(), duration); - - if (stats.getAiSequence().isInCombat() && !stats.isDead()) hostilesCount++; } } @@ -1283,9 +1324,24 @@ namespace MWMechanics CharacterController* playerCharacter = NULL; for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { - if (iter->first != player && - (player.getRefData().getPosition().asVec3() - iter->first.getRefData().getPosition().asVec3()).length2() - > sqrAiProcessingDistance) + const float animationDistance = aiProcessingDistance + 400; // Slightly larger than AI distance so there is time to switch back to the idle animation. + const float distSqr = (player.getRefData().getPosition().asVec3() - iter->first.getRefData().getPosition().asVec3()).length2(); + bool isPlayer = iter->first == player; + bool inAnimationRange = isPlayer || (animationDistance == 0 || distSqr <= animationDistance*animationDistance); + int activeFlag = 1; // Can be changed back to '2' to keep updating bounding boxes off screen (more accurate, but slower) + if (isPlayer) + activeFlag = 2; + int active = inAnimationRange ? activeFlag : 0; + bool canFly = iter->first.getClass().canFly(iter->first); + if (canFly) + { + // Keep animating flying creatures so they don't just hover in-air + inAnimationRange = true; + active = std::max(1, active); + } + iter->second->getCharacterController()->setActive(active); + + if (!inAnimationRange) continue; if (iter->first.getClass().getCreatureStats(iter->first).isParalyzed()) @@ -1325,21 +1381,6 @@ namespace MWMechanics killDeadActors(); - // check if we still have any player enemies to switch music - static int currentMusic = 0; - - if (currentMusic != 1 && hostilesCount == 0 && !(player.getClass().getCreatureStats(player).isDead() && - MWBase::Environment::get().getSoundManager()->isMusicPlaying())) - { - MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); - currentMusic = 1; - } - else if (currentMusic != 2 && hostilesCount > 0) - { - MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Battle")); - currentMusic = 2; - } - static float sneakTimer = 0.f; // times update of sneak icon // if player is in sneak state see if anyone detects him @@ -1406,6 +1447,8 @@ namespace MWMechanics MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); } } + + updateCombatMusic(); } void Actors::killDeadActors() @@ -1621,6 +1664,17 @@ namespace MWMechanics } } + bool Actors::isAnyObjectInRange(const osg::Vec3f& position, float radius) + { + for (PtrActorMap::iterator iter = mActors.begin(); iter != mActors.end(); ++iter) + { + if ((iter->first.getRefData().getPosition().asVec3() - position).length2() <= radius*radius) + return true; + } + + return false; + } + std::list Actors::getActorsSidingWith(const MWWorld::Ptr& actor) { std::list list; diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index e433434a5..13641abf4 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -81,6 +81,9 @@ namespace MWMechanics void dropActors (const MWWorld::CellStore *cellStore, const MWWorld::Ptr& ignore); ///< Deregister all actors (except for \a ignore) in the given cell. + void updateCombatMusic(); + ///< Update combat music state + void update (float duration, bool paused); ///< Update actor stats and store desired velocity vectors in \a movement @@ -115,15 +118,17 @@ namespace MWMechanics bool isRunning(const MWWorld::Ptr& ptr); bool isSneaking(const MWWorld::Ptr& ptr); - void forceStateUpdate(const MWWorld::Ptr &ptr); + void forceStateUpdate(const MWWorld::Ptr &ptr); - bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist=false); - void skipAnimation(const MWWorld::Ptr& ptr); - bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName); - void persistAnimationStates(); + bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist=false); + void skipAnimation(const MWWorld::Ptr& ptr); + bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName); + void persistAnimationStates(); void getObjectsInRange(const osg::Vec3f& position, float radius, std::vector& out); + bool isAnyObjectInRange(const osg::Vec3f& position, float radius); + void cleanupSummonedCreature (CreatureStats& casterStats, int creatureActorId); ///Returns the list of actors which are siding with the given actor in fights diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 68d282d0c..83ebc67d9 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -13,6 +13,7 @@ #include "../mwrender/animation.hpp" +#include "pathgrid.hpp" #include "creaturestats.hpp" #include "steering.hpp" #include "movement.hpp" @@ -372,7 +373,7 @@ namespace MWMechanics int closestPointIndex = PathFinder::GetClosestPoint(pathgrid, localPos); for (int i = 0; i < static_cast(pathgrid->mPoints.size()); i++) { - if (i != closestPointIndex && storage.mCell->isPointConnected(closestPointIndex, i)) + if (i != closestPointIndex && getPathGridGraph(storage.mCell).isPointConnected(closestPointIndex, i)) { points.push_back(pathgrid->mPoints[static_cast(i)]); } @@ -490,6 +491,22 @@ namespace MWMechanics void AiCombatStorage::startCombatMove(bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, const MWWorld::Ptr& target) { + // get the range of the target's weapon + MWWorld::Ptr targetWeapon = MWWorld::Ptr(); + const MWWorld::Class& targetClass = target.getClass(); + + if (targetClass.hasInventoryStore(target)) + { + MWMechanics::WeaponType weapType = WeapType_None; + MWWorld::ContainerStoreIterator weaponSlot = + MWMechanics::getActiveWeapon(targetClass.getCreatureStats(target), targetClass.getInventoryStore(target), &weapType); + if (weapType != WeapType_PickProbe && weapType != WeapType_Spell && weapType != WeapType_None && weapType != WeapType_HandToHand) + targetWeapon = *weaponSlot; + } + + bool targetUsesRanged = false; + float rangeAttackOfTarget = ActionWeapon(targetWeapon).getCombatRange(targetUsesRanged); + if (mMovement.mPosition[0] || mMovement.mPosition[1]) { mTimerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(); @@ -498,28 +515,6 @@ namespace MWMechanics // dodge movements (for NPCs and bipedal creatures) else if (actor.getClass().isBipedal(actor)) { - // get the range of the target's weapon - float rangeAttackOfTarget = 0.f; - MWWorld::Ptr targetWeapon = MWWorld::Ptr(); - const MWWorld::Class& targetClass = target.getClass(); - - if (targetClass.hasInventoryStore(target)) - { - MWMechanics::WeaponType weapType = WeapType_None; - MWWorld::ContainerStoreIterator weaponSlot = - MWMechanics::getActiveWeapon(targetClass.getCreatureStats(target), targetClass.getInventoryStore(target), &weapType); - if (weapType != WeapType_PickProbe && weapType != WeapType_Spell && weapType != WeapType_None && weapType != WeapType_HandToHand) - targetWeapon = *weaponSlot; - } - - std::shared_ptr targetWeaponAction (new ActionWeapon(targetWeapon)); - - if (targetWeaponAction.get()) - { - bool isRangedCombat = false; - rangeAttackOfTarget = targetWeaponAction->getCombatRange(isRangedCombat); - } - // apply sideway movement (kind of dodging) with some probability // if actor is within range of target's weapon if (distToTarget <= rangeAttackOfTarget && Misc::Rng::rollClosedProbability() < 0.25) @@ -530,16 +525,18 @@ namespace MWMechanics } } - // Below behavior for backing up during ranged combat differs from vanilla. - // Vanilla is observed as backing up only as far as fCombatDistance or - // opponent's weapon range, or not backing up if opponent is also using a ranged weapon - if (isDistantCombat && distToTarget < rangeAttack / 4) + // Backing up behaviour + // Actor backs up slightly further away than opponent's weapon range + // (in vanilla - only as far as oponent's weapon range), + // or not at all if opponent is using a ranged weapon + if (isDistantCombat) { // actor should not back up into water if (MWBase::Environment::get().getWorld()->isUnderwater(MWWorld::ConstPtr(actor), 0.5f)) return; - mMovement.mPosition[1] = -1; + if (!targetUsesRanged && distToTarget <= rangeAttackOfTarget*1.5) // Don't back up if the target is wielding ranged weapon + mMovement.mPosition[1] = -1; } } diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index d44498966..0ccd0b6ec 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -115,6 +115,7 @@ namespace MWMechanics isRanged = false; static const float fCombatDistance = MWBase::Environment::get().getWorld()->getStore().get().find("fCombatDistance")->getFloat(); + static const float fProjectileMaxSpeed = MWBase::Environment::get().getWorld()->getStore().get().find("fProjectileMaxSpeed")->getFloat(); if (mWeapon.isEmpty()) { @@ -128,7 +129,7 @@ namespace MWMechanics if (weapon->mData.mType >= ESM::Weapon::MarksmanBow) { isRanged = true; - return 1000.f; + return fProjectileMaxSpeed; } else return weapon->mData.mReach * fCombatDistance; @@ -181,29 +182,11 @@ namespace MWMechanics } } - float bestArrowRating = 0; MWWorld::Ptr bestArrow; - for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) - { - float rating = rateWeapon(*it, actor, enemy, ESM::Weapon::Arrow); - if (rating > bestArrowRating) - { - bestArrowRating = rating; - bestArrow = *it; - } - } + float bestArrowRating = rateAmmo(actor, enemy, bestArrow, ESM::Weapon::Arrow); - float bestBoltRating = 0; MWWorld::Ptr bestBolt; - for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) - { - float rating = rateWeapon(*it, actor, enemy, ESM::Weapon::Bolt); - if (rating > bestBoltRating) - { - bestBoltRating = rating; - bestBolt = *it; - } - } + float bestBoltRating = rateAmmo(actor, enemy, bestBolt, ESM::Weapon::Bolt); for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { @@ -276,25 +259,9 @@ namespace MWMechanics } } - float bestArrowRating = 0; - for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) - { - float rating = rateWeapon(*it, actor, enemy, ESM::Weapon::Arrow); - if (rating > bestArrowRating) - { - bestArrowRating = rating; - } - } + float bestArrowRating = rateAmmo(actor, enemy, ESM::Weapon::Arrow); - float bestBoltRating = 0; - for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) - { - float rating = rateWeapon(*it, actor, enemy, ESM::Weapon::Bolt); - if (rating > bestBoltRating) - { - bestBoltRating = rating; - } - } + float bestBoltRating = rateAmmo(actor, enemy, ESM::Weapon::Bolt); for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index f837ad2ee..398e84448 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -14,6 +14,7 @@ #include "../mwworld/cellstore.hpp" #include "../mwworld/inventorystore.hpp" +#include "pathgrid.hpp" #include "creaturestats.hpp" #include "movement.hpp" #include "steering.hpp" @@ -100,14 +101,23 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr { bool wasShortcutting = mIsShortcutting; bool destInLOS = false; - if (getTypeId() != TypeIdWander) // prohibit shortcuts for AiWander - mIsShortcutting = shortcutPath(start, dest, actor, &destInLOS); // try to shortcut first + + const MWWorld::Class& actorClass = actor.getClass(); + MWBase::World* world = MWBase::Environment::get().getWorld(); + + // check if actor can move along z-axis + bool actorCanMoveByZ = (actorClass.canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor)) + || world->isFlying(actor); + + // Prohibit shortcuts for AiWander, if the actor can not move in 3 dimensions. + if (actorCanMoveByZ || getTypeId() != TypeIdWander) + mIsShortcutting = shortcutPath(start, dest, actor, &destInLOS, actorCanMoveByZ); // try to shortcut first if (!mIsShortcutting) { if (wasShortcutting || doesPathNeedRecalc(dest, actor.getCell())) // if need to rebuild path { - mPathFinder.buildSyncedPath(start, dest, actor.getCell()); + mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell())); mRotateOnTheRunChecks = 3; // give priority to go directly on target if there is minimal opportunity @@ -220,20 +230,23 @@ void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, float dur } } -bool MWMechanics::AiPackage::shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS) +const MWMechanics::PathgridGraph& MWMechanics::AiPackage::getPathGridGraph(const MWWorld::CellStore *cell) { - const MWWorld::Class& actorClass = actor.getClass(); - MWBase::World* world = MWBase::Environment::get().getWorld(); - - // check if actor can move along z-axis - bool actorCanMoveByZ = (actorClass.canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor)) - || world->isFlying(actor); - - // don't use pathgrid when actor can move in 3 dimensions - bool isPathClear = actorCanMoveByZ; + const ESM::CellId& id = cell->getCell()->getCellId(); + // static cache is OK for now, pathgrids can never change during runtime + typedef std::map > CacheMap; + static CacheMap cache; + CacheMap::iterator found = cache.find(id); + if (found == cache.end()) + { + cache.insert(std::make_pair(id, std::unique_ptr(new MWMechanics::PathgridGraph(cell)))); + } + return *cache[id].get(); +} - if (!isPathClear - && (!mShortcutProhibited || (PathFinder::MakeOsgVec3(mShortcutFailPos) - PathFinder::MakeOsgVec3(startPoint)).length() >= PATHFIND_SHORTCUT_RETRY_DIST)) +bool MWMechanics::AiPackage::shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS, bool isPathClear) +{ + if (!mShortcutProhibited || (PathFinder::MakeOsgVec3(mShortcutFailPos) - PathFinder::MakeOsgVec3(startPoint)).length() >= PATHFIND_SHORTCUT_RETRY_DIST) { // check if target is clearly visible isPathClear = !MWBase::Environment::get().getWorld()->castRay( diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index acbd87908..06b4adf61 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -27,6 +27,7 @@ namespace MWMechanics const float AI_REACTION_TIME = 0.25f; class CharacterController; + class PathgridGraph; /// \brief Base class for AI packages class AiPackage @@ -110,7 +111,7 @@ namespace MWMechanics /// If a shortcut is possible then path will be cleared and filled with the destination point. /// \param destInLOS If not NULL function will return ray cast check result /// \return If can shortcut the path - bool shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS); + bool shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS, bool isPathClear); /// Check if the way to the destination is clear, taking into account actor speed bool checkWayIsClearForActor(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor); @@ -119,6 +120,8 @@ namespace MWMechanics void evadeObstacles(const MWWorld::Ptr& actor, float duration, const ESM::Position& pos); + const PathgridGraph& getPathGridGraph(const MWWorld::CellStore* cell); + // TODO: all this does not belong here, move into temporary storage PathFinder mPathFinder; ObstacleCheck mObstacleCheck; diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index f9884eb6d..fd8b5752a 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -50,8 +50,13 @@ bool AiPursue::execute (const MWWorld::Ptr& actor, CharacterController& characte //Set the target desition from the actor ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; + ESM::Position aPos = actor.getRefData().getPosition(); - if (pathTo(actor, dest, duration, 100)) { + float pathTolerance = 100.0; + + if (pathTo(actor, dest, duration, pathTolerance) && + std::abs(dest.mZ - aPos.pos[2]) < pathTolerance) // check the true distance in case the target is far away in Z-direction + { target.getClass().activate(target,actor).get()->execute(actor); //Arrest player when reached return true; } diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 255874d88..8199170dc 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -17,6 +17,7 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" +#include "pathgrid.hpp" #include "creaturestats.hpp" #include "steering.hpp" #include "movement.hpp" @@ -217,7 +218,7 @@ namespace MWMechanics ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mDestination)); ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); - mPathFinder.buildSyncedPath(start, dest, actor.getCell()); + mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell())); if (mPathFinder.isPathConstructed()) storage.setState(Wander_Walking); @@ -264,9 +265,20 @@ namespace MWMechanics getAllowedNodes(actor, currentCell->getCell(), storage); } + bool actorCanMoveByZ = (actor.getClass().canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor)) + || MWBase::Environment::get().getWorld()->isFlying(actor); + + if(actorCanMoveByZ && mDistance > 0) { + // Typically want to idle for a short time before the next wander + if (Misc::Rng::rollDice(100) >= 92 && storage.mState != Wander_Walking) { + wanderNearStart(actor, storage, mDistance); + } + + storage.mCanWanderAlongPathGrid = false; + } // If the package has a wander distance but no pathgrid is available, // randomly idle or wander near spawn point - if(storage.mAllowedNodes.empty() && mDistance > 0 && !storage.mIsWanderingManually) { + else 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); @@ -349,7 +361,7 @@ namespace MWMechanics ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); // don't take shortcuts for wandering - mPathFinder.buildSyncedPath(start, dest, actor.getCell()); + mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell())); if (mPathFinder.isPathConstructed()) { @@ -372,7 +384,7 @@ namespace MWMechanics do { // Determine a random location within radius of original position const float pi = 3.14159265359f; - const float wanderRadius = Misc::Rng::rollClosedProbability() * wanderDistance; + const float wanderRadius = (0.2f + Misc::Rng::rollClosedProbability() * 0.8f) * 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); @@ -383,7 +395,7 @@ namespace MWMechanics // Check if land creature will walk onto water or if water creature will swim onto land if ((!isWaterCreature && !destinationIsAtWater(actor, mDestination)) || (isWaterCreature && !destinationThroughGround(currentPositionVec3f, mDestination))) { - mPathFinder.buildSyncedPath(currentPosition, destinationPosition, actor.getCell()); + mPathFinder.buildSyncedPath(currentPosition, destinationPosition, actor.getCell(), getPathGridGraph(actor.getCell())); mPathFinder.addPointToPath(destinationPosition); if (mPathFinder.isPathConstructed()) @@ -660,13 +672,14 @@ namespace MWMechanics { 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 coordinates ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(actorPos)); // don't take shortcuts for wandering - mPathFinder.buildSyncedPath(start, dest, actor.getCell()); + mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell())); if (mPathFinder.isPathConstructed()) { @@ -799,20 +812,80 @@ namespace MWMechanics int index = Misc::Rng::rollDice(storage.mAllowedNodes.size()); ESM::Pathgrid::Point dest = storage.mAllowedNodes[index]; - state.moveIn(new AiWanderStorage()); + ESM::Pathgrid::Point worldDest = dest; + ToWorldCoordinates(worldDest, actor.getCell()->getCell()); + + bool isPathGridOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(PathFinder::MakeOsgVec3(worldDest), 60); + + // add offset only if the selected pathgrid is occupied by another actor + if (isPathGridOccupied) + { + ESM::Pathgrid::PointList points; + getNeighbouringNodes(dest, actor.getCell(), points); + + // there are no neighbouring nodes, nowhere to move + if (points.empty()) + return; + + int initialSize = points.size(); + bool isOccupied = false; + // AI will try to move the NPC towards every neighboring node until suitable place will be found + for (int i = 0; i < initialSize; i++) + { + int randomIndex = Misc::Rng::rollDice(points.size()); + ESM::Pathgrid::Point connDest = points[randomIndex]; + + // add an offset towards random neighboring node + osg::Vec3f dir = PathFinder::MakeOsgVec3(connDest) - PathFinder::MakeOsgVec3(dest); + float length = dir.length(); + dir.normalize(); + + for (int j = 1; j <= 3; j++) + { + // move for 5-15% towards random neighboring node + dest = PathFinder::MakePathgridPoint(PathFinder::MakeOsgVec3(dest) + dir * (j * 5 * length / 100.f)); + worldDest = dest; + ToWorldCoordinates(worldDest, actor.getCell()->getCell()); + + isOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(PathFinder::MakeOsgVec3(worldDest), 60); + + if (!isOccupied) + break; + } + + if (!isOccupied) + break; + + // Will try an another neighboring node + points.erase(points.begin()+randomIndex); + } + + // there is no free space, nowhere to move + if (isOccupied) + return; + } + + // place above to prevent moving inside objects, e.g. stairs, because a vector between pathgrids can be underground. + // Adding 20 in adjustPosition() is not enough. + dest.mZ += 60; - dest.mX += OffsetToPreventOvercrowding(); - dest.mY += OffsetToPreventOvercrowding(); ToWorldCoordinates(dest, actor.getCell()->getCell()); + state.moveIn(new AiWanderStorage()); + MWBase::Environment::get().getWorld()->moveObject(actor, static_cast(dest.mX), static_cast(dest.mY), static_cast(dest.mZ)); actor.getClass().adjustPosition(actor, false); } - int AiWander::OffsetToPreventOvercrowding() + void AiWander::getNeighbouringNodes(ESM::Pathgrid::Point dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points) { - return static_cast(20 * (Misc::Rng::rollProbability() * 2.0f - 1.0f)); + const ESM::Pathgrid *pathgrid = + MWBase::Environment::get().getWorld()->getStore().get().search(*currentCell->getCell()); + + int index = PathFinder::GetClosestPoint(pathgrid, PathFinder::MakeOsgVec3(dest)); + + getPathGridGraph(currentCell).getNeighbouringPoints(index, points); } void AiWander::getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell, AiWanderStorage& storage) @@ -853,7 +926,7 @@ namespace MWMechanics { osg::Vec3f nodePos(PathFinder::MakeOsgVec3(pathgrid->mPoints[counter])); if((npcPos - nodePos).length2() <= mDistance * mDistance && - cellStore->isPointConnected(closestPointIndex, counter)) + getPathGridGraph(cellStore).isPointConnected(closestPointIndex, counter)) { storage.mAllowedNodes.push_back(pathgrid->mPoints[counter]); pointIndex = counter; diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 01d889e2f..6266a7708 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -104,6 +104,8 @@ namespace MWMechanics bool mHasDestination; osg::Vec3f mDestination; + void getNeighbouringNodes(ESM::Pathgrid::Point dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points); + void getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell, AiWanderStorage& storage); void trimAllowedNodes(std::vector& nodes, const PathFinder& pathfinder); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 03acfdaf2..676f01922 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -254,6 +254,7 @@ void CharacterController::refreshHitRecoilAnims() || mPtr.getClass().getCreatureStats(mPtr).getFatigue().getBase() == 0) && mAnimation->hasAnimation("knockout")) { + mTimeUntilWake = Misc::Rng::rollClosedProbability() * 2 + 1; // Wake up after 1 to 3 seconds if (isSwimming && mAnimation->hasAnimation("swimknockout")) { mHitState = CharState_SwimKnockOut; @@ -338,7 +339,8 @@ void CharacterController::refreshHitRecoilAnims() mPtr.getClass().getCreatureStats(mPtr).setBlock(false); mHitState = CharState_None; } - else if (isKnockedOut() && mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() > 0) + else if (isKnockedOut() && mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() > 0 + && mTimeUntilWake <= 0) { mHitState = isSwimming ? CharState_SwimKnockDown : CharState_KnockDown; mAnimation->disable(mCurrentHit); @@ -372,21 +374,29 @@ void CharacterController::refreshJumpAnims(const WeaponInfo* weap, JumpingState } } - if(mJumpState == JumpState_InAir) + if (!mCurrentJump.empty()) { mAnimation->disable(mCurrentJump); - mCurrentJump = jumpAnimName; - if (mAnimation->hasAnimation("jump")) - mAnimation->play(mCurrentJump, Priority_Jump, jumpmask, false, + mCurrentJump.clear(); + } + + if(mJumpState == JumpState_InAir) + { + if (mAnimation->hasAnimation(jumpAnimName)) + { + mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, false, 1.0f, (startAtLoop?"loop start":"start"), "stop", 0.0f, ~0ul); + mCurrentJump = jumpAnimName; + } } - else + else if (mJumpState == JumpState_Landing) { - mAnimation->disable(mCurrentJump); - mCurrentJump.clear(); - if (mAnimation->hasAnimation("jump")) + if (mAnimation->hasAnimation(jumpAnimName)) + { mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, true, 1.0f, "loop stop", "stop", 0.0f, 0); + mCurrentJump = jumpAnimName; + } } } } @@ -759,6 +769,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim , mSecondsOfRunning(0) , mTurnAnimationThreshold(0) , mAttackingOrSpell(false) + , mTimeUntilWake(0.f) { if(!mAnimation) return; @@ -1267,7 +1278,16 @@ bool CharacterController::updateWeaponState() bool animPlaying; if(mAttackingOrSpell) { - mIdleState = CharState_None; + MWWorld::Ptr player = getPlayer(); + + // We should reset player's idle animation in the first-person mode. + if (mPtr == player && MWBase::Environment::get().getWorld()->isFirstPerson()) + mIdleState = CharState_None; + + // In other cases we should not break swim and sneak animations + if (mIdleState != CharState_IdleSneak && mIdleState != CharState_IdleSwim) + mIdleState = CharState_None; + if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block)) { MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); @@ -1277,7 +1297,7 @@ bool CharacterController::updateWeaponState() // Unset casting flag, otherwise pressing the mouse button down would // continue casting every frame if there is no animation mAttackingOrSpell = false; - if (mPtr == getPlayer()) + if (mPtr == player) { MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false); } @@ -1287,7 +1307,7 @@ bool CharacterController::updateWeaponState() // For the player, set the spell we want to cast // This has to be done at the start of the casting animation, // *not* when selecting a spell in the GUI (otherwise you could change the spell mid-animation) - if (mPtr == getPlayer()) + if (mPtr == player) { std::string selectedSpell = MWBase::Environment::get().getWindowManager()->getSelectedSpell(); stats.getSpells().setSelectedSpell(selectedSpell); @@ -1299,7 +1319,7 @@ bool CharacterController::updateWeaponState() MWMechanics::CastSpell cast(mPtr, NULL); cast.playSpellCastingEffects(spellid); - const ESM::Spell *spell = store.get().find(spellid); + const ESM::Spell *spell = store.get().find(spellid); const ESM::ENAMstruct &lastEffect = spell->mEffects.mList.back(); const ESM::MagicEffect *effect; @@ -1629,6 +1649,9 @@ void CharacterController::update(float duration) updateMagicEffects(); + if (isKnockedOut()) + mTimeUntilWake -= duration; + bool godmode = mPtr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); if(!cls.isActor()) @@ -1692,7 +1715,6 @@ void CharacterController::update(float duration) mHasMovedInXY = std::abs(vec.x())+std::abs(vec.y()) > 0.0f; isrunning = isrunning && mHasMovedInXY; - // advance athletics if(mHasMovedInXY && mPtr == getPlayer()) { @@ -1775,7 +1797,7 @@ void CharacterController::update(float duration) vec.y() *= factor; vec.z() = 0.0f; } - else if(vec.z() > 0.0f && mJumpState == JumpState_None) + else if(vec.z() > 0.0f && mJumpState != JumpState_InAir) { // Started a jump. float z = cls.getJump(mPtr); @@ -1847,7 +1869,8 @@ void CharacterController::update(float duration) } else { - jumpstate = JumpState_None; + jumpstate = mAnimation->isPlaying(mCurrentJump) ? JumpState_Landing : JumpState_None; + vec.z() = 0.0f; inJump = false; @@ -1877,9 +1900,15 @@ void CharacterController::update(float duration) else if(rot.z() != 0.0f && !sneak && !(mPtr == getPlayer() && MWBase::Environment::get().getWorld()->isFirstPerson())) { if(rot.z() > 0.0f) + { movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight; + mAnimation->disable(mCurrentJump); + } else if(rot.z() < 0.0f) + { movestate = inwater ? CharState_SwimTurnLeft : CharState_TurnLeft; + mAnimation->disable(mCurrentJump); + } } } @@ -2363,7 +2392,7 @@ float CharacterController::getAttackStrength() const return mAttackStrength; } -void CharacterController::setActive(bool active) +void CharacterController::setActive(int active) { mAnimation->setActive(active); } diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index af90c18b8..cab51b82f 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -204,6 +204,8 @@ class CharacterController : public MWRender::Animation::TextKeyListener bool mAttackingOrSpell; + float mTimeUntilWake; + void setAttackTypeBasedOnMovement(); void refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force=false); @@ -290,7 +292,7 @@ public: float getAttackStrength() const; /// @see Animation::setActive - void setActive(bool active); + void setActive(int active); /// Make this character turn its head towards \a target. To turn off head tracking, pass an empty Ptr. void setHeadTrackTarget(const MWWorld::ConstPtr& target); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index dfbcf0ea2..bd5fa1b11 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -566,9 +566,16 @@ namespace MWMechanics { std::string itFaction = playerFactionIt->first; + // Ignore the faction, if a player was expelled from it. + if (playerStats.getExpelled(itFaction)) + continue; + int itReaction = MWBase::Environment::get().getDialogueManager()->getFactionReaction(npcFaction, itFaction); if (playerFactionIt == playerStats.getFactionRanks().begin() || itReaction < reaction) + { reaction = static_cast(itReaction); + rank = playerFactionIt->second; + } } } else @@ -840,6 +847,45 @@ namespace MWMechanics mAI = true; } + bool MechanicsManager::isBoundItem(const MWWorld::Ptr& item) + { + static std::set boundItemIDCache; + + // If this is empty then we haven't executed the GMST cache logic yet; or there isn't any sMagicBound* GMST's for some reason + if (boundItemIDCache.empty()) + { + // Build a list of known bound item ID's + const MWWorld::Store &gameSettings = MWBase::Environment::get().getWorld()->getStore().get(); + + for (MWWorld::Store::iterator currentIteration = gameSettings.begin(); currentIteration != gameSettings.end(); ++currentIteration) + { + const ESM::GameSetting ¤tSetting = *currentIteration; + std::string currentGMSTID = currentSetting.mId; + Misc::StringUtils::lowerCaseInPlace(currentGMSTID); + + // Don't bother checking this GMST if it's not a sMagicBound* one. + const std::string& toFind = "smagicbound"; + if (currentGMSTID.compare(0, toFind.length(), toFind) != 0) + continue; + + // All sMagicBound* GMST's should be of type string + std::string currentGMSTValue = currentSetting.getString(); + Misc::StringUtils::lowerCaseInPlace(currentGMSTValue); + + boundItemIDCache.insert(currentGMSTValue); + } + } + + // Perform bound item check and assign the Flag_Bound bit if it passes + std::string tempItemID = item.getCellRef().getRefId(); + Misc::StringUtils::lowerCaseInPlace(tempItemID); + + if (boundItemIDCache.count(tempItemID) != 0) + return true; + + return false; + } + bool MechanicsManager::isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, MWWorld::Ptr& victim) { if (target.isEmpty()) @@ -1024,7 +1070,7 @@ namespace MWMechanics } void MechanicsManager::itemTaken(const MWWorld::Ptr &ptr, const MWWorld::Ptr &item, const MWWorld::Ptr& container, - int count) + int count, bool alarm) { if (ptr != getPlayer()) return; @@ -1053,19 +1099,29 @@ namespace MWMechanics return; Owner owner; - owner.first = ownerCellRef->getOwner(); owner.second = false; - if (owner.first.empty()) + if (!container.isEmpty() && container.getClass().isActor()) + { + // "container" is an actor inventory, so just take actor's ID + owner.first = ownerCellRef->getRefId(); + } + else { - owner.first = ownerCellRef->getFaction(); - owner.second = true; + owner.first = ownerCellRef->getOwner(); + if (owner.first.empty()) + { + owner.first = ownerCellRef->getFaction(); + owner.second = true; + } } + Misc::StringUtils::lowerCaseInPlace(owner.first); if (!Misc::StringUtils::ciEqual(item.getCellRef().getRefId(), MWWorld::ContainerStore::sGoldId)) mStolenItems[Misc::StringUtils::lowerCase(item.getCellRef().getRefId())][owner] += count; - commitCrime(ptr, victim, OT_Theft, item.getClass().getValue(item) * count); + if (alarm) + commitCrime(ptr, victim, OT_Theft, item.getClass().getValue(item) * count); } @@ -1529,6 +1585,11 @@ namespace MWMechanics mActors.getObjectsInRange(position, radius, objects); } + bool MechanicsManager::isAnyActorInRange(const osg::Vec3f &position, float radius) + { + return mActors.isAnyObjectInRange(position, radius); + } + std::list MechanicsManager::getActorsSidingWith(const MWWorld::Ptr& actor) { return mActors.getActorsSidingWith(actor); @@ -1599,6 +1660,12 @@ namespace MWMechanics bool MechanicsManager::isAggressive(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target) { + // Don't become aggressive if a calm effect is active, since it would cause combat to cycle on/off as + // combat is activated here and then canceled by the calm effect + if ((ptr.getClass().isNpc() && ptr.getClass().getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::CalmHumanoid).getMagnitude() > 0) + || (!ptr.getClass().isNpc() && ptr.getClass().getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::CalmCreature).getMagnitude() > 0)) + return false; + int disposition = 50; if (ptr.getClass().isNpc()) disposition = getDerivedDisposition(ptr, true); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index ee4cf28af..4bf47cb16 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -135,7 +135,7 @@ namespace MWMechanics /// Utility to check if taking this item is illegal and calling commitCrime if so /// @param container The container the item is in; may be empty for an item in the world virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container, - int count); + int count, bool alarm = true); /// Utility to check if opening (i.e. unlocking) this object is illegal and calling commitCrime if so virtual void objectOpened (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item); /// Attempt sleeping in a bed. If this is illegal, call commitCrime. @@ -158,6 +158,9 @@ namespace MWMechanics virtual void getObjectsInRange (const osg::Vec3f& position, float radius, std::vector& objects); virtual void getActorsInRange(const osg::Vec3f &position, float radius, std::vector &objects); + /// Check if there are actors in selected range + virtual bool isAnyActorInRange(const osg::Vec3f &position, float radius); + virtual std::list getActorsSidingWith(const MWWorld::Ptr& actor); virtual std::list getActorsFollowing(const MWWorld::Ptr& actor); virtual std::list getActorsFollowingIndices(const MWWorld::Ptr& actor); @@ -204,6 +207,8 @@ namespace MWMechanics /// Has the player stolen this item from the given owner? virtual bool isItemStolenFrom(const std::string& itemid, const std::string& ownerid); + virtual bool isBoundItem(const MWWorld::Ptr& item); + /// @return is \a ptr allowed to take/use \a target or is it a crime? virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, MWWorld::Ptr& victim); diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 5c0456096..1dccb6c9a 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -5,16 +5,16 @@ #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" -#include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" +#include "pathgrid.hpp" #include "coordinateconverter.hpp" namespace { // Chooses a reachable end pathgrid point. start is assumed reachable. std::pair getClosestReachablePoint(const ESM::Pathgrid* grid, - const MWWorld::CellStore *cell, + const MWMechanics::PathgridGraph *graph, const osg::Vec3f& pos, int start) { assert(grid && !grid->mPoints.empty()); @@ -31,7 +31,7 @@ namespace if (potentialDistBetween < closestDistanceReachable) { // found a closer one - if (cell->isPointConnected(start, counter)) + if (graph->isPointConnected(start, counter)) { closestDistanceReachable = potentialDistBetween; closestReachableIndex = counter; @@ -45,7 +45,7 @@ namespace } // post-condition: start and endpoint must be connected - assert(cell->isPointConnected(start, closestReachableIndex)); + assert(graph->isPointConnected(start, closestReachableIndex)); // AiWander has logic that depends on whether a path was created, deleting // allowed nodes if not. Hence a path needs to be created even if the start @@ -120,8 +120,8 @@ namespace MWMechanics } PathFinder::PathFinder() - : mPathgrid(NULL), - mCell(NULL) + : mPathgrid(NULL) + , mCell(NULL) { } @@ -169,14 +169,15 @@ namespace MWMechanics */ void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const MWWorld::CellStore* cell) + const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph) { mPath.clear(); + // TODO: consider removing mCell / mPathgrid in favor of mPathgridGraph if(mCell != cell || !mPathgrid) { mCell = cell; - mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*mCell->getCell()); + mPathgrid = pathgridGraph.getPathgrid(); } // Refer to AiWander reseach topic on openmw forums for some background. @@ -200,7 +201,7 @@ namespace MWMechanics int startNode = GetClosestPoint(mPathgrid, startPointInLocalCoords); osg::Vec3f endPointInLocalCoords(converter.toLocalVec3(endPoint)); - std::pair endNode = getClosestReachablePoint(mPathgrid, cell, + std::pair endNode = getClosestReachablePoint(mPathgrid, &pathgridGraph, endPointInLocalCoords, startNode); @@ -228,7 +229,7 @@ namespace MWMechanics } else { - mPath = mCell->aStarSearch(startNode, endNode.first); + mPath = pathgridGraph.aStarSearch(startNode, endNode.first); // convert supplied path to world coordinates for (std::list::iterator iter(mPath.begin()); iter != mPath.end(); ++iter) @@ -301,18 +302,18 @@ namespace MWMechanics // see header for the rationale void PathFinder::buildSyncedPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const MWWorld::CellStore* cell) + const MWWorld::CellStore* cell, const MWMechanics::PathgridGraph& pathgridGraph) { if (mPath.size() < 2) { // if path has one point, then it's the destination. // don't need to worry about bad path for this case - buildPath(startPoint, endPoint, cell); + buildPath(startPoint, endPoint, cell, pathgridGraph); } else { const ESM::Pathgrid::Point oldStart(*getPath().begin()); - buildPath(startPoint, endPoint, cell); + buildPath(startPoint, endPoint, cell, pathgridGraph); if (mPath.size() >= 2) { // if 2nd waypoint of new path == 1st waypoint of old, diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 945a7f927..ebc22f10d 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -14,6 +14,8 @@ namespace MWWorld namespace MWMechanics { + class PathgridGraph; + float distance(const ESM::Pathgrid::Point& point, float x, float y, float); float distance(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b); float getZAngleToDir(const osg::Vec3f& dir); @@ -54,7 +56,7 @@ namespace MWMechanics void clearPath(); void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const MWWorld::CellStore* cell); + const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph); bool checkPathCompleted(float x, float y, float tolerance = PathTolerance); ///< \Returns true if we are within \a tolerance units of the last path point. @@ -89,7 +91,7 @@ namespace MWMechanics Which results in NPC "running in a circle" back to the just passed waypoint. */ void buildSyncedPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const MWWorld::CellStore* cell); + const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph); void addPointToPath(const ESM::Pathgrid::Point &point) { diff --git a/apps/openmw/mwmechanics/pathgrid.cpp b/apps/openmw/mwmechanics/pathgrid.cpp index c557beadd..c0122a861 100644 --- a/apps/openmw/mwmechanics/pathgrid.cpp +++ b/apps/openmw/mwmechanics/pathgrid.cpp @@ -49,7 +49,7 @@ namespace namespace MWMechanics { - PathgridGraph::PathgridGraph() + PathgridGraph::PathgridGraph(const MWWorld::CellStore *cell) : mCell(NULL) , mPathgrid(NULL) , mIsExterior(0) @@ -58,6 +58,7 @@ namespace MWMechanics , mSCCId(0) , mSCCIndex(0) { + load(cell); } /* @@ -130,6 +131,11 @@ namespace MWMechanics return true; } + const ESM::Pathgrid *PathgridGraph::getPathgrid() const + { + return mPathgrid; + } + // v is the pathgrid point index (some call them vertices) void PathgridGraph::recursiveStrongConnect(int v) { @@ -214,6 +220,16 @@ namespace MWMechanics return (mGraph[start].componentId == mGraph[end].componentId); } + void PathgridGraph::getNeighbouringPoints(const int index, ESM::Pathgrid::PointList &nodes) const + { + for(int i = 0; i < static_cast (mGraph[index].edges.size()); i++) + { + int neighbourIndex = mGraph[index].edges[i].index; + if (neighbourIndex != index) + nodes.push_back(mPathgrid->mPoints[neighbourIndex]); + } + } + /* * NOTE: Based on buildPath2(), please check git history if interested * Should consider using a 3rd party library version (e.g. boost) diff --git a/apps/openmw/mwmechanics/pathgrid.hpp b/apps/openmw/mwmechanics/pathgrid.hpp index d90cb47cd..0c71c4561 100644 --- a/apps/openmw/mwmechanics/pathgrid.hpp +++ b/apps/openmw/mwmechanics/pathgrid.hpp @@ -20,14 +20,19 @@ namespace MWMechanics class PathgridGraph { public: - PathgridGraph(); + PathgridGraph(const MWWorld::CellStore* cell); bool load(const MWWorld::CellStore *cell); + const ESM::Pathgrid* getPathgrid() const; + // returns true if end point is strongly connected (i.e. reachable // from start point) both start and end are pathgrid point indexes bool isPointConnected(const int start, const int end) const; + // get neighbouring nodes for index node and put them to "nodes" vector + void getNeighbouringPoints(const int index, ESM::Pathgrid::PointList &nodes) const; + // the input parameters are pathgrid point indexes // the output list is in local (internal cells) or world (external // cells) coordinates diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index a09436d14..d864dc619 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -115,7 +115,7 @@ namespace MWMechanics return castChance; } - float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap) + float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap, bool checkMagicka) { bool godmode = actor == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); @@ -135,6 +135,9 @@ namespace MWMechanics if (spell->mData.mType != ESM::Spell::ST_Spell) return 100; + if (checkMagicka && stats.getMagicka().getCurrent() < spell->mData.mCost && !godmode) + return 0; + if (spell->mData.mFlags & ESM::Spell::F_Always) return 100; @@ -149,11 +152,11 @@ namespace MWMechanics return std::max(0.f, std::min(100.f, castChance)); } - float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap) + float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap, bool checkMagicka) { const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellId); - return getSpellSuccessChance(spell, actor, effectiveSchool, cap); + return getSpellSuccessChance(spell, actor, effectiveSchool, cap, checkMagicka); } @@ -965,9 +968,9 @@ namespace MWMechanics float y = roll / std::min(x, 100.f); y *= 0.25f * x; if (magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) - effect.mDuration = static_cast(y); - else effect.mDuration = 1; + else + effect.mDuration = static_cast(y); if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) { if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 2e368afcf..1f5ef45bd 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -31,11 +31,12 @@ namespace MWMechanics * @param actor calculate spell success chance for this actor (depends on actor's skills) * @param effectiveSchool the spell's effective school (relevant for skill progress) will be written here * @param cap cap the result to 100%? + * @param checkMagicka check magicka? * @note actor can be an NPC or a creature * @return success chance from 0 to 100 (in percent), if cap=false then chance above 100 may be returned. */ - float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = NULL, bool cap=true); - float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = NULL, bool cap=true); + float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = NULL, bool cap=true, bool checkMagicka=false); + float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = NULL, bool cap=true, bool checkMagicka=false); int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor); int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor); diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index 3a730fbeb..7aef54007 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -1,4 +1,5 @@ #include "spellpriority.hpp" +#include "weaponpriority.hpp" #include #include @@ -335,6 +336,12 @@ namespace MWMechanics return 0.f; break; + case ESM::MagicEffect::BoundLongbow: + // AI should not summon the bow if there is no suitable ammo. + if (rateAmmo(actor, enemy, ESM::Weapon::Arrow) <= 0.f) + return 0.f; + break; + case ESM::MagicEffect::RestoreHealth: case ESM::MagicEffect::RestoreMagicka: case ESM::MagicEffect::RestoreFatigue: diff --git a/apps/openmw/mwmechanics/weaponpriority.cpp b/apps/openmw/mwmechanics/weaponpriority.cpp index d06e73c93..5a0bd9c15 100644 --- a/apps/openmw/mwmechanics/weaponpriority.cpp +++ b/apps/openmw/mwmechanics/weaponpriority.cpp @@ -8,6 +8,7 @@ #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/inventorystore.hpp" #include "npcstats.hpp" #include "combat.hpp" @@ -111,6 +112,33 @@ namespace MWMechanics return rating + bonus; } + float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, MWWorld::Ptr &bestAmmo, ESM::Weapon::Type ammoType) + { + float bestAmmoRating = 0.f; + if (!actor.getClass().hasInventoryStore(actor)) + return bestAmmoRating; + + MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); + + for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) + { + float rating = rateWeapon(*it, actor, enemy, ammoType); + if (rating > bestAmmoRating) + { + bestAmmoRating = rating; + bestAmmo = *it; + } + } + + return bestAmmoRating; + } + + float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, ESM::Weapon::Type ammoType) + { + MWWorld::Ptr emptyPtr; + return rateAmmo(actor, enemy, emptyPtr, ammoType); + } + float vanillaRateWeaponAndAmmo(const MWWorld::Ptr& weapon, const MWWorld::Ptr& ammo, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy) { const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); diff --git a/apps/openmw/mwmechanics/weaponpriority.hpp b/apps/openmw/mwmechanics/weaponpriority.hpp index 90a767c4a..f5c9a1159 100644 --- a/apps/openmw/mwmechanics/weaponpriority.hpp +++ b/apps/openmw/mwmechanics/weaponpriority.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_WEAPON_PRIORITY_H #define OPENMW_WEAPON_PRIORITY_H +#include + #include "../mwworld/ptr.hpp" namespace MWMechanics @@ -8,6 +10,9 @@ namespace MWMechanics 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); + float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, MWWorld::Ptr &bestAmmo, ESM::Weapon::Type ammoType); + float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, ESM::Weapon::Type ammoType); + float vanillaRateWeaponAndAmmo(const MWWorld::Ptr& weapon, const MWWorld::Ptr& ammo, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 36041a85e..174560e6b 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -705,7 +705,7 @@ namespace MWPhysics if (physFramerate > 0) { mPhysicsDt = 1.f / physFramerate; - std::cerr << "Warning: physics framerate was overriden (a new value is " << physFramerate << ")." << std::endl; + std::cerr << "Warning: physics framerate was overridden (a new value is " << physFramerate << ")." << std::endl; } } } diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 90b69de8e..1bd839ead 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -486,10 +486,10 @@ namespace MWRender return mPtr; } - void Animation::setActive(bool active) + void Animation::setActive(int active) { if (mSkeleton) - mSkeleton->setActive(active); + mSkeleton->setActive(static_cast(active)); } void Animation::updatePtr(const MWWorld::Ptr &ptr) @@ -536,7 +536,7 @@ namespace MWRender return mKeyframes->mTextKeys; } - void Animation::addAnimSource(const std::string &model) + void Animation::addAnimSource(const std::string &model, const std::string& baseModel) { std::string kfname = model; Misc::StringUtils::lowerCaseInPlace(kfname); @@ -565,7 +565,7 @@ namespace MWRender NodeMap::const_iterator found = nodeMap.find(bonename); if (found == nodeMap.end()) { - std::cerr << "Warning: addAnimSource: can't find bone '" + bonename << "' in " << model << " (referenced by " << kfname << ")" << std::endl; + std::cerr << "Warning: addAnimSource: can't find bone '" + bonename << "' in " << baseModel << " (referenced by " << kfname << ")" << std::endl; continue; } @@ -625,7 +625,7 @@ namespace MWRender float Animation::getStartTime(const std::string &groupname) const { - for(AnimSourceList::const_iterator iter(mAnimSources.begin()); iter != mAnimSources.end(); ++iter) + for(AnimSourceList::const_reverse_iterator iter(mAnimSources.rbegin()); iter != mAnimSources.rend(); ++iter) { const NifOsg::TextKeyMap &keys = (*iter)->getTextKeys(); @@ -638,7 +638,7 @@ namespace MWRender float Animation::getTextKeyTime(const std::string &textKey) const { - for(AnimSourceList::const_iterator iter(mAnimSources.begin()); iter != mAnimSources.end(); ++iter) + for(AnimSourceList::const_reverse_iterator iter(mAnimSources.rbegin()); iter != mAnimSources.rend(); ++iter) { const NifOsg::TextKeyMap &keys = (*iter)->getTextKeys(); @@ -1668,7 +1668,7 @@ namespace MWRender { setObjectRoot(model, false, false, false); if (animated) - addAnimSource(model); + addAnimSource(model, model); if (!ptr.getClass().getEnchantment(ptr).empty()) addGlow(mObjectRoot, getEnchantmentColor(ptr)); diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 5aec80f5d..cff98a4b7 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -309,11 +309,12 @@ 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 .nif - * file extension will be replaced with .kf. + /** Adds the keyframe controllers in the specified model as a new animation source. * @note Later added animation sources have the highest priority when it comes to finding a particular animation. + * @param model The file to add the keyframes for. Note that the .nif file extension will be replaced with .kf. + * @param baseModel The filename of the mObjectRoot, only used for error messages. */ - void addAnimSource(const std::string &model); + void addAnimSource(const std::string &model, const std::string& baseModel); /** Adds an additional light to the given node using the specified ESM record. */ void addExtraLight(osg::ref_ptr parent, const ESM::Light *light); @@ -346,7 +347,8 @@ public: /// Set active flag on the object skeleton, if one exists. /// @see SceneUtil::Skeleton::setActive - void setActive(bool active); + /// 0 = Inactive, 1 = Active in place, 2 = Active + void setActive(int active); osg::Group* getOrCreateObjectRoot(); diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 735c0b66d..827b576c3 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -33,8 +33,8 @@ CreatureAnimation::CreatureAnimation(const MWWorld::Ptr &ptr, setObjectRoot(model, false, false, true); if((ref->mBase->mFlags&ESM::Creature::Bipedal)) - addAnimSource("meshes\\xbase_anim.nif"); - addAnimSource(model); + addAnimSource("meshes\\xbase_anim.nif", model); + addAnimSource(model, model); } } @@ -51,8 +51,8 @@ CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr, const setObjectRoot(model, true, false, true); if((ref->mBase->mFlags&ESM::Creature::Bipedal)) - addAnimSource("meshes\\xbase_anim.nif"); - addAnimSource(model); + addAnimSource("meshes\\xbase_anim.nif", model); + addAnimSource(model, model); mPtr.getClass().getInventoryStore(mPtr).setInvListener(this, mPtr); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 8211c2f5f..08e376f08 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -388,9 +388,6 @@ void NpcAnimation::rebuild() { updateNpcBase(); - if (mAlpha != 1.f) - mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot); - MWBase::Environment::get().getMechanicsManager()->forceStateUpdate(mPtr); } @@ -485,25 +482,25 @@ void NpcAnimation::updateNpcBase() { const std::string base = "meshes\\xbase_anim.nif"; if (smodel != base) - addAnimSource(base); + addAnimSource(base, smodel); - addAnimSource(smodel); + addAnimSource(smodel, smodel); if(!isWerewolf) { if(mNpc->mModel.length() > 0) - addAnimSource(Misc::ResourceHelpers::correctActorModelPath("meshes\\" + mNpc->mModel, mResourceSystem->getVFS())); + addAnimSource(Misc::ResourceHelpers::correctActorModelPath("meshes\\" + mNpc->mModel, mResourceSystem->getVFS()), smodel); if(Misc::StringUtils::lowerCase(mNpc->mRace).find("argonian") != std::string::npos) - addAnimSource("meshes\\xargonian_swimkna.nif"); + addAnimSource("meshes\\xargonian_swimkna.nif", smodel); } } else { const std::string base = "meshes\\xbase_anim.1st.nif"; if (smodel != base) - addAnimSource(base); + addAnimSource(base, smodel); - addAnimSource(smodel); + addAnimSource(smodel, smodel); mObjectRoot->setNodeMask(Mask_FirstPerson); mObjectRoot->addCullCallback(new OverrideFieldOfViewCallback(mFirstPersonFieldOfView)); @@ -651,6 +648,9 @@ void NpcAnimation::updateParts() if (wasArrowAttached) attachArrow(); + + if (mAlpha != 1.f) + mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot); } @@ -917,6 +917,8 @@ void NpcAnimation::showWeapons(bool showWeapon) attachArrow(); } } + if (mAlpha != 1.f) + mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot); } else { @@ -942,6 +944,8 @@ void NpcAnimation::showCarriedLeft(bool show) if (iter->getTypeName() == typeid(ESM::Light).name() && mObjectParts[ESM::PRT_Shield]) addExtraLight(mObjectParts[ESM::PRT_Shield]->getNode()->asGroup(), iter->get()->mBase); } + if (mAlpha != 1.f) + mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot); } else removeIndividualPart(ESM::PRT_Shield); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 3b9100126..295f33287 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -215,7 +215,10 @@ namespace MWRender mObjects.reset(new Objects(mResourceSystem, sceneRoot, mUnrefQueue.get())); if (getenv("OPENMW_DONT_PRECOMPILE") == NULL) + { mViewer->setIncrementalCompileOperation(new osgUtil::IncrementalCompileOperation); + mViewer->getIncrementalCompileOperation()->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells")); + } mResourceSystem->getSceneManager()->setIncrementalCompileOperation(mViewer->getIncrementalCompileOperation()); @@ -233,6 +236,7 @@ namespace MWRender else mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile)); mTerrain->setDefaultViewer(mViewer->getCamera()); + mTerrain->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells")); mCamera.reset(new Camera(mViewer->getCamera())); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 6c599fc3f..2e4329f69 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -1203,6 +1203,18 @@ void SkyManager::create() mCreated = true; } +class RainCounter : public osgParticle::ConstantRateCounter +{ +public: + virtual int numParticlesToCreate(double dt) const + { + // limit dt to avoid large particle emissions if there are jumps in the simulation time + // 0.2 seconds is the same cap as used in Engine's frame loop + dt = std::min(dt, 0.2); + return ConstantRateCounter::numParticlesToCreate(dt); + } +}; + class RainShooter : public osgParticle::Shooter { public: @@ -1473,7 +1485,7 @@ void SkyManager::createRain() placer->setZRange(300, 300); emitter->setPlacer(placer); - osg::ref_ptr counter (new osgParticle::ConstantRateCounter); + osg::ref_ptr counter (new RainCounter); counter->setNumberOfParticlesPerSecondToCreate(600.0); emitter->setCounter(counter); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index bfd671971..36ff40279 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -272,6 +272,12 @@ public: void setWaterLevel(float waterLevel) { + const float refractionScale = std::min(1.0f,std::max(0.0f, + Settings::Manager::getFloat("refraction scale", "Water"))); + + setViewMatrix(osg::Matrix::scale(1,1,refractionScale) * + osg::Matrix::translate(0,0,(1.0 - refractionScale) * waterLevel)); + mClipCullNode->setPlane(osg::Plane(osg::Vec3d(0,0,-1), osg::Vec3d(0,0, waterLevel))); } diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index 26d7916b1..d70ff5319 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -463,7 +463,7 @@ namespace MWScript const MWWorld::Ptr ref = MWBase::Environment::get().getWorld()->getPtr(name, false); // If the objects are in different worldspaces, return a large value (just like vanilla) - if (ref.getCell()->getCell()->getCellId().mWorldspace != ref2.getCell()->getCell()->getCellId().mWorldspace) + if (!ref.isInCell() || !ref2.isInCell() || ref.getCell()->getCell()->getCellId().mWorldspace != ref2.getCell()->getCell()->getCellId().mWorldspace) return std::numeric_limits::max(); double diff[3]; diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index c876d4cf7..68b1063e4 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -202,11 +202,6 @@ namespace MWScript if (!ptr.isInCell()) return; - if (ptr == MWMechanics::getPlayer()) - { - MWBase::Environment::get().getWorld()->getPlayer().setTeleported(true); - } - std::string axis = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); Interpreter::Type_Float pos = runtime[0].mFloat; @@ -216,6 +211,8 @@ namespace MWScript float ay = ptr.getRefData().getPosition().pos[1]; float az = ptr.getRefData().getPosition().pos[2]; + // Note: SetPos does not skip weather transitions in vanilla engine, so we do not call setTeleported(true) here. + MWWorld::Ptr updated = ptr; if(axis == "x") { @@ -227,6 +224,17 @@ namespace MWScript } else if(axis == "z") { + // We should not place actors under ground + if (ptr.getClass().isActor()) + { + float terrainHeight = -std::numeric_limits::max(); + if (ptr.getCell()->isExterior()) + terrainHeight = MWBase::Environment::get().getWorld()->getTerrainHeightAt(osg::Vec3f(ax, ay, az)); + + if (pos < terrainHeight) + pos = terrainHeight; + } + updated = MWBase::Environment::get().getWorld()->moveObject(ptr,ax,ay,pos); } else diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index 1f1834761..e2d54876f 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include #include @@ -11,11 +11,6 @@ namespace MWSound { -void FFmpeg_Decoder::fail(const std::string &msg) -{ - throw std::runtime_error("FFmpeg exception: "+msg); -} - int FFmpeg_Decoder::readPacket(void *user_data, uint8_t *buf, int buf_size) { try @@ -33,7 +28,8 @@ int FFmpeg_Decoder::readPacket(void *user_data, uint8_t *buf, int buf_size) int FFmpeg_Decoder::writePacket(void *, uint8_t *, int) { - throw std::runtime_error("can't write to read-only stream"); + std::cerr<< "can't write to read-only stream" <get(fname); if((mFormatCtx=avformat_alloc_context()) == NULL) - fail("Failed to allocate context"); + throw std::runtime_error("Failed to allocate context"); - mFormatCtx->pb = avio_alloc_context(NULL, 0, 0, this, readPacket, writePacket, seek); - if(!mFormatCtx->pb || avformat_open_input(&mFormatCtx, fname.c_str(), NULL, NULL) != 0) + try { - // "Note that a user-supplied AVFormatContext will be freed on failure". - if (mFormatCtx) + mFormatCtx->pb = avio_alloc_context(NULL, 0, 0, this, readPacket, writePacket, seek); + if(!mFormatCtx->pb || avformat_open_input(&mFormatCtx, fname.c_str(), NULL, NULL) != 0) { - if (mFormatCtx->pb != NULL) + // "Note that a user-supplied AVFormatContext will be freed on failure". + if (mFormatCtx) { - if (mFormatCtx->pb->buffer != NULL) + if (mFormatCtx->pb != NULL) { - av_free(mFormatCtx->pb->buffer); - mFormatCtx->pb->buffer = NULL; + if (mFormatCtx->pb->buffer != NULL) + { + av_free(mFormatCtx->pb->buffer); + mFormatCtx->pb->buffer = NULL; + } + av_free(mFormatCtx->pb); + mFormatCtx->pb = NULL; } - av_free(mFormatCtx->pb); - mFormatCtx->pb = NULL; + avformat_free_context(mFormatCtx); } - avformat_free_context(mFormatCtx); + mFormatCtx = NULL; + throw std::runtime_error("Failed to allocate input stream"); } - mFormatCtx = NULL; - fail("Failed to allocate input stream"); - } - try - { if(avformat_find_stream_info(mFormatCtx, NULL) < 0) - fail("Failed to find stream info in "+fname); + throw std::runtime_error("Failed to find stream info in "+fname); for(size_t j = 0;j < mFormatCtx->nb_streams;j++) { @@ -224,32 +220,46 @@ void FFmpeg_Decoder::open(const std::string &fname) } } if(!mStream) - fail("No audio streams in "+fname); + throw std::runtime_error("No audio streams in "+fname); (*mStream)->codec->request_sample_fmt = (*mStream)->codec->sample_fmt; AVCodec *codec = avcodec_find_decoder((*mStream)->codec->codec_id); if(!codec) { - std::stringstream ss("No codec found for id "); - ss << (*mStream)->codec->codec_id; - fail(ss.str()); + std::string ss = "No codec found for id " + + std::to_string((*mStream)->codec->codec_id); + throw std::runtime_error(ss); } if(avcodec_open2((*mStream)->codec, codec, NULL) < 0) - fail("Failed to open audio codec " + std::string(codec->long_name)); + throw std::runtime_error(std::string("Failed to open audio codec ") + + codec->long_name); mFrame = av_frame_alloc(); + + if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_FLT || + (*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_FLTP) + mOutputSampleFormat = AV_SAMPLE_FMT_S16; // FIXME: Check for AL_EXT_FLOAT32 support + else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_U8P) + mOutputSampleFormat = AV_SAMPLE_FMT_U8; + else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_S16P) + mOutputSampleFormat = AV_SAMPLE_FMT_S16; + else + mOutputSampleFormat = AV_SAMPLE_FMT_S16; + + mOutputChannelLayout = (*mStream)->codec->channel_layout; + if(mOutputChannelLayout == 0) + mOutputChannelLayout = av_get_default_channel_layout((*mStream)->codec->channels); } - catch(std::exception&) - { + catch(...) { if(mStream) avcodec_close((*mStream)->codec); mStream = NULL; if (mFormatCtx->pb->buffer != NULL) { - av_free(mFormatCtx->pb->buffer); - mFormatCtx->pb->buffer = NULL; + av_free(mFormatCtx->pb->buffer); + mFormatCtx->pb->buffer = NULL; } av_free(mFormatCtx->pb); mFormatCtx->pb = NULL; @@ -274,18 +284,18 @@ void FFmpeg_Decoder::close() { if (mFormatCtx->pb != NULL) { - // mFormatCtx->pb->buffer must be freed by hand, - // if not, valgrind will show memleak, see: - // - // https://trac.ffmpeg.org/ticket/1357 - // - if (mFormatCtx->pb->buffer != NULL) - { - av_free(mFormatCtx->pb->buffer); - mFormatCtx->pb->buffer = NULL; - } - av_free(mFormatCtx->pb); - mFormatCtx->pb = NULL; + // mFormatCtx->pb->buffer must be freed by hand, + // if not, valgrind will show memleak, see: + // + // https://trac.ffmpeg.org/ticket/1357 + // + if (mFormatCtx->pb->buffer != NULL) + { + av_free(mFormatCtx->pb->buffer); + mFormatCtx->pb->buffer = NULL; + } + av_free(mFormatCtx->pb); + mFormatCtx->pb = NULL; } avformat_close_input(&mFormatCtx); } @@ -301,16 +311,7 @@ std::string FFmpeg_Decoder::getName() void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) { if(!mStream) - fail("No audio stream info"); - - if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_FLT || (*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_FLTP) - mOutputSampleFormat = AV_SAMPLE_FMT_S16; // FIXME: Check for AL_EXT_FLOAT32 support - else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_U8P) - mOutputSampleFormat = AV_SAMPLE_FMT_U8; - else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_S16P) - mOutputSampleFormat = AV_SAMPLE_FMT_S16; - else - mOutputSampleFormat = AV_SAMPLE_FMT_S16; + throw std::runtime_error("No audio stream info"); if(mOutputSampleFormat == AV_SAMPLE_FMT_U8) *type = SampleType_UInt8; @@ -318,19 +319,11 @@ void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType * *type = SampleType_Int16; else if(mOutputSampleFormat == AV_SAMPLE_FMT_FLT) *type = SampleType_Float32; - - int64_t ch_layout = (*mStream)->codec->channel_layout; - - if(ch_layout == 0) - ch_layout = av_get_default_channel_layout((*mStream)->codec->channels); - - mOutputChannelLayout = ch_layout; - if (ch_layout == AV_CH_LAYOUT_5POINT1 || ch_layout == AV_CH_LAYOUT_7POINT1 - || ch_layout == AV_CH_LAYOUT_QUAD) // FIXME: check for AL_EXT_MCFORMATS support - mOutputChannelLayout = AV_CH_LAYOUT_STEREO; - else if (ch_layout != AV_CH_LAYOUT_MONO - && ch_layout != AV_CH_LAYOUT_STEREO) - mOutputChannelLayout = AV_CH_LAYOUT_STEREO; + else + { + mOutputSampleFormat = AV_SAMPLE_FMT_S16; + *type = SampleType_Int16; + } if(mOutputChannelLayout == AV_CH_LAYOUT_MONO) *chans = ChannelConfig_Mono; @@ -347,13 +340,27 @@ void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType * char str[1024]; av_get_channel_layout_string(str, sizeof(str), (*mStream)->codec->channels, (*mStream)->codec->channel_layout); - fail(std::string("Unsupported channel layout: ")+str); + std::cerr<< "Unsupported channel layout: "<codec->channels == 1) + { + mOutputChannelLayout = AV_CH_LAYOUT_MONO; + *chans = ChannelConfig_Mono; + } + else + { + mOutputChannelLayout = AV_CH_LAYOUT_STEREO; + *chans = ChannelConfig_Stereo; + } } *samplerate = (*mStream)->codec->sample_rate; + int64_t ch_layout = (*mStream)->codec->channel_layout; + if(ch_layout == 0) + ch_layout = av_get_default_channel_layout((*mStream)->codec->channels); - if(mOutputSampleFormat != (*mStream)->codec->sample_fmt - || mOutputChannelLayout != ch_layout) + if(mOutputSampleFormat != (*mStream)->codec->sample_fmt || + mOutputChannelLayout != ch_layout) { mSwr = swr_alloc_set_opts(mSwr, // SwrContext mOutputChannelLayout, // output ch layout @@ -365,24 +372,29 @@ void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType * 0, // logging level offset NULL); // log context if(!mSwr) - fail(std::string("Couldn't allocate SwrContext")); + throw std::runtime_error("Couldn't allocate SwrContext"); if(swr_init(mSwr) < 0) - fail(std::string("Couldn't initialize SwrContext")); - + throw std::runtime_error("Couldn't initialize SwrContext"); } } size_t FFmpeg_Decoder::read(char *buffer, size_t bytes) { if(!mStream) - fail("No audio stream"); + { + std::cerr<< "No audio stream" < &output) { if(!mStream) - fail("No audio stream"); + { + std::cerr<< "No audio stream" < &output); - virtual size_t getSampleOffset(); - - void fail(const std::string &msg); + size_t read(char *buffer, size_t bytes) override; + void readAll(std::vector &output) override; + size_t getSampleOffset() override; FFmpeg_Decoder& operator=(const FFmpeg_Decoder &rhs); FFmpeg_Decoder(const FFmpeg_Decoder &rhs); diff --git a/apps/openmw/mwsound/movieaudiofactory.cpp b/apps/openmw/mwsound/movieaudiofactory.cpp index f54ab5c06..497562516 100644 --- a/apps/openmw/mwsound/movieaudiofactory.cpp +++ b/apps/openmw/mwsound/movieaudiofactory.cpp @@ -1,5 +1,7 @@ #include "movieaudiofactory.hpp" +#include + #include #include @@ -13,7 +15,7 @@ namespace MWSound { class MovieAudioDecoder; - class MWSoundDecoderBridge : public Sound_Decoder + class MWSoundDecoderBridge final : public Sound_Decoder { public: MWSoundDecoderBridge(MWSound::MovieAudioDecoder* decoder) @@ -25,12 +27,12 @@ namespace MWSound private: MWSound::MovieAudioDecoder* mDecoder; - virtual void open(const std::string &fname); - virtual void close(); - virtual std::string getName(); - virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type); - virtual size_t read(char *buffer, size_t bytes); - virtual size_t getSampleOffset(); + void open(const std::string &fname) override; + void close() override; + std::string getName() override; + void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) override; + size_t read(char *buffer, size_t bytes) override; + size_t getSampleOffset() override; }; class MovieAudioDecoder : public Video::MovieAudioDecoder @@ -98,7 +100,7 @@ namespace MWSound void MWSoundDecoderBridge::open(const std::string &fname) { - throw std::runtime_error("unimplemented"); + throw std::runtime_error("Method not implemented"); } void MWSoundDecoderBridge::close() {} @@ -123,11 +125,8 @@ namespace MWSound else if (outputChannelLayout == AV_CH_LAYOUT_QUAD) *chans = ChannelConfig_Quad; else - { - std::stringstream error; - error << "Unsupported channel layout: " << outputChannelLayout; - throw std::runtime_error(error.str()); - } + throw std::runtime_error("Unsupported channel layout: "+ + std::to_string(outputChannelLayout)); AVSampleFormat outputSampleFormat = mDecoder->getOutputSampleFormat(); if (outputSampleFormat == AV_SAMPLE_FMT_U8) @@ -140,7 +139,7 @@ namespace MWSound { char str[1024]; av_get_sample_fmt_string(str, sizeof(str), outputSampleFormat); - throw std::runtime_error(std::string("Unsupported sample format: ") + str); + throw std::runtime_error(std::string("Unsupported sample format: ")+str); } } diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 3fb1c1c13..829c001e5 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -411,9 +411,14 @@ bool OpenAL_SoundStream::init(bool getLoudnessData) ChannelConfig chans; SampleType type; - mDecoder->getInfo(&mSampleRate, &chans, &type); - mFormat = getALFormat(chans, type); - if(!mFormat) return false; + try { + mDecoder->getInfo(&mSampleRate, &chans, &type); + mFormat = getALFormat(chans, type); + } + catch(std::exception &e) { + std::cerr<< "Failed to get stream info: "< OpenAL_Output::loadSound(const std::string &fname { getALError(); - DecoderPtr decoder = mManager.getDecoder(); - // Workaround: Bethesda at some point converted some of the files to mp3, but the references were kept as .wav. - if(decoder->mResourceMgr->exists(fname)) - decoder->open(fname); - else - { - std::string file = fname; - std::string::size_type pos = file.rfind('.'); - if(pos != std::string::npos) - file = file.substr(0, pos)+".mp3"; - decoder->open(file); - } - std::vector data; - ChannelConfig chans; - SampleType type; ALenum format; int srate; - decoder->getInfo(&srate, &chans, &type); - format = getALFormat(chans, type); - if(!format) return std::make_pair(nullptr, 0); + try { + DecoderPtr decoder = mManager.getDecoder(); + // Workaround: Bethesda at some point converted some of the files to mp3, but the references were kept as .wav. + if(decoder->mResourceMgr->exists(fname)) + decoder->open(fname); + else + { + std::string file = fname; + std::string::size_type pos = file.rfind('.'); + if(pos != std::string::npos) + file = file.substr(0, pos)+".mp3"; + decoder->open(file); + } + + ChannelConfig chans; + SampleType type; + decoder->getInfo(&srate, &chans, &type); + format = getALFormat(chans, type); + if(format) decoder->readAll(data); + } + catch(std::exception &e) { + std::cerr<< "Failed to load audio from "<readAll(data); - decoder->close(); + if(data.empty()) + { + // If we failed to get any usable audio, substitute with silence. + format = AL_FORMAT_MONO8; + srate = 8000; + data.assign(8000, -128); + } ALint size; ALuint buf = 0; diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index b4f260a25..f33c7bb67 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -445,10 +445,6 @@ namespace MWWorld loadRefs (); mState = State_Loaded; - - // TODO: the pathgrid graph only needs to be loaded for active cells, so move this somewhere else. - // In a simple test, loading the graph for all cells in MW + expansions took 200 ms - mPathgridGraph.load(this); } } @@ -937,16 +933,6 @@ namespace MWWorld return !(left==right); } - bool CellStore::isPointConnected(const int start, const int end) const - { - return mPathgridGraph.isPointConnected(start, end); - } - - std::list CellStore::aStarSearch(const int start, const int end) const - { - return mPathgridGraph.aStarSearch(start, end); - } - void CellStore::setFog(ESM::FogState *fog) { mFogState.reset(fog); diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 4452ad889..dd54bdd6a 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -32,13 +32,12 @@ #include #include -#include "../mwmechanics/pathgrid.hpp" // TODO: maybe belongs in mwworld - #include "timestamp.hpp" #include "ptr.hpp" namespace ESM { + struct Cell; struct CellState; struct FogState; struct CellId; @@ -376,10 +375,6 @@ namespace MWWorld void respawn (); ///< Check mLastRespawn and respawn references if necessary. This is a no-op if the cell is not loaded. - bool isPointConnected(const int start, const int end) const; - - std::list aStarSearch(const int start, const int end) const; - private: /// Run through references and store IDs @@ -391,8 +386,6 @@ namespace MWWorld ///< Make case-adjustments to \a ref and insert it into the respective container. /// /// Invalid \a ref objects are silently dropped. - - MWMechanics::PathgridGraph mPathgridGraph; }; template<> diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index cd04a425b..981f63e34 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -806,7 +806,7 @@ void MWWorld::ContainerStore::readState (const ESM::InventoryState& inventory) case ESM::REC_WEAP: readEquipmentState (getState (weapons, state), thisIndex, inventory); break; case ESM::REC_LIGH: readEquipmentState (getState (lights, state), thisIndex, inventory); break; case 0: - std::cerr << "Warning: Dropping reference to '" << state.mRef.mRefID << "' (object no longer exists)" << std::endl; + std::cerr << "Dropping inventory reference to '" << state.mRef.mRefID << "' (object no longer exists)" << std::endl; break; default: std::cerr << "Warning: Invalid item type in inventory state, refid " << state.mRef.mRefID << std::endl; diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index ede8c34c4..a4a22ea4a 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -202,6 +203,12 @@ namespace MWWorld osg::ref_ptr projectile = mResourceSystem->getSceneManager()->getInstance(model, attachTo); + osg::ref_ptr boundVisitor = new osg::ComputeBoundsVisitor(); + projectile->accept(*boundVisitor.get()); + osg::BoundingBox bb = boundVisitor->getBoundingBox(); + + state.mNode->setPivotPoint(bb.center()); + if (state.mIdMagic.size() > 1) for (size_t iter = 1; iter != state.mIdMagic.size(); ++iter) { @@ -316,6 +323,7 @@ namespace MWWorld state.mIdArrow = projectile.getCellRef().getRefId(); state.mCasterHandle = actor; state.mAttackStrength = attackStrength; + state.mThrown = projectile.get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown; MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), projectile.getCellRef().getRefId()); MWWorld::Ptr ptr = ref.getPtr(); @@ -378,7 +386,6 @@ namespace MWWorld static float fTargetSpellMaxSpeed = MWBase::Environment::get().getWorld()->getStore().get() .find("fTargetSpellMaxSpeed")->getFloat(); float speed = fTargetSpellMaxSpeed * it->mSpeed; - osg::Vec3f direction = orient * osg::Vec3f(0,1,0); direction.normalize(); osg::Vec3f pos(it->mNode->getPosition()); @@ -458,7 +465,22 @@ namespace MWWorld osg::Vec3f newPos = pos + it->mVelocity * duration; osg::Quat orient; - orient.makeRotate(osg::Vec3f(0,1,0), it->mVelocity); + + if (it->mThrown) + orient.set( + osg::Matrixd::rotate(it->mEffectAnimationTime->getTime() * -10.0,osg::Vec3f(0,0,1)) * + osg::Matrixd::rotate(osg::PI / 2.0,osg::Vec3f(0,1,0)) * + osg::Matrixd::rotate(-1 * osg::PI / 2.0,osg::Vec3f(1,0,0)) * + osg::Matrixd::inverse( + osg::Matrixd::lookAt( + osg::Vec3f(0,0,0), + it->mVelocity, + osg::Vec3f(0,0,1)) + ) + ); + else + orient.makeRotate(osg::Vec3f(0,1,0), it->mVelocity); + it->mNode->setAttitude(orient); it->mNode->setPosition(newPos); @@ -596,6 +618,7 @@ namespace MWWorld MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), esm.mId); MWWorld::Ptr ptr = ref.getPtr(); model = ptr.getClass().getModel(ptr); + state.mThrown = ptr.get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown; } catch(...) { diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index 0dfbf6080..ba2fe7a74 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -112,6 +112,7 @@ namespace MWWorld osg::Vec3f mVelocity; float mAttackStrength; + bool mThrown; }; std::vector mMagicBolts; diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 704dbf970..9279e3fe7 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -607,6 +607,11 @@ namespace MWWorld } return ptr; } + void Store::clearDynamic() + { + setUp(); + } + void Store::setUp() { typedef DynamicExt::iterator ExtIterator; diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 3babea86a..00ea6f66f 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -293,6 +293,7 @@ namespace MWWorld const ESM::Cell *find(const std::string &id) const; const ESM::Cell *find(int x, int y) const; + virtual void clearDynamic(); void setUp(); RecordId load(ESM::ESMReader &esm); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 1f8b01eac..def9595c0 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -148,16 +148,16 @@ namespace MWWorld ToUTF8::Utf8Encoder* encoder, const std::map& fallbackMap, int activationDistanceOverride, const std::string& startCell, const std::string& startupScript, const std::string& resourcePath, const std::string& userDataPath) - : mResourceSystem(resourceSystem), mFallback(fallbackMap), mPlayer (0), mLocalScripts (mStore), + : mResourceSystem(resourceSystem), mFallback(fallbackMap), mLocalScripts (mStore), mSky (true), mCells (mStore, mEsm), mGodMode(false), mScriptsEnabled(true), mContentFiles (contentFiles), mUserDataPath(userDataPath), mActivationDistanceOverride (activationDistanceOverride), mStartupScript(startupScript), mStartCell (startCell), mDistanceToFacedObject(-1), mTeleportEnabled(true), mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0), mSpellPreloadTimer(0.f) { - mPhysics = new MWPhysics::PhysicsSystem(resourceSystem, rootNode); - mRendering = new MWRender::RenderingManager(viewer, rootNode, resourceSystem, workQueue, &mFallback, resourcePath); - mProjectileManager.reset(new ProjectileManager(mRendering->getLightRoot(), resourceSystem, mRendering, mPhysics)); + mPhysics.reset(new MWPhysics::PhysicsSystem(resourceSystem, rootNode)); + mRendering.reset(new MWRender::RenderingManager(viewer, rootNode, resourceSystem, workQueue, &mFallback, resourcePath)); + mProjectileManager.reset(new ProjectileManager(mRendering->getLightRoot(), resourceSystem, mRendering.get(), mPhysics.get())); mRendering->preloadCommonAssets(); @@ -189,9 +189,9 @@ namespace MWWorld mSwimHeightScale = mStore.get().find("fSwimHeightScale")->getFloat(); - mWeatherManager = new MWWorld::WeatherManager(*mRendering, mFallback, mStore); + mWeatherManager.reset(new MWWorld::WeatherManager(*mRendering, mFallback, mStore)); - mWorldScene = new Scene(*mRendering, mPhysics); + mWorldScene.reset(new Scene(*mRendering.get(), mPhysics.get())); } void World::fillGlobalVariables() @@ -224,8 +224,8 @@ namespace MWWorld // we don't want old weather to persist on a new game // Note that if reset later, the initial ChangeWeather that the chargen script calls will be lost. - delete mWeatherManager; - mWeatherManager = new MWWorld::WeatherManager(*mRendering, mFallback, mStore); + mWeatherManager.reset(); + mWeatherManager.reset(new MWWorld::WeatherManager(*mRendering.get(), mFallback, mStore)); if (!bypass) { @@ -295,7 +295,6 @@ namespace MWWorld mWorldScene->clear(); mStore.clearDynamic(); - mStore.setUp(); if (mPlayer) { @@ -435,6 +434,7 @@ namespace MWWorld gmst["fNPCHealthBarFade"] = ESM::Variant(1.f); gmst["fFleeDistance"] = ESM::Variant(3000.f); gmst["sMaxSale"] = ESM::Variant("Max Sale"); + gmst["sAnd"] = ESM::Variant("and"); // Werewolf (BM) gmst["fWereWolfRunMult"] = ESM::Variant(1.3f); @@ -491,12 +491,6 @@ namespace MWWorld { // Must be cleared before mRendering is destroyed mProjectileManager->clear(); - delete mWeatherManager; - delete mWorldScene; - delete mRendering; - delete mPhysics; - - delete mPlayer; } const ESM::Cell *World::getExterior (const std::string& cellName) const @@ -1757,19 +1751,26 @@ namespace MWWorld void World::updateWindowManager () { - // inform the GUI about focused object - MWWorld::Ptr object = getFacedObject (); - - // retrieve object dimensions so we know where to place the floating label - if (!object.isEmpty ()) + try { - osg::Vec4f screenBounds = mRendering->getScreenBounds(object); + // inform the GUI about focused object + MWWorld::Ptr object = getFacedObject (); - MWBase::Environment::get().getWindowManager()->setFocusObjectScreenCoords( - screenBounds.x(), screenBounds.y(), screenBounds.z(), screenBounds.w()); - } + // retrieve object dimensions so we know where to place the floating label + if (!object.isEmpty ()) + { + osg::Vec4f screenBounds = mRendering->getScreenBounds(object); - MWBase::Environment::get().getWindowManager()->setFocusObject(object); + MWBase::Environment::get().getWindowManager()->setFocusObjectScreenCoords( + screenBounds.x(), screenBounds.y(), screenBounds.z(), screenBounds.w()); + } + + MWBase::Environment::get().getWindowManager()->setFocusObject(object); + } + catch (std::exception& e) + { + std::cerr << "Error updating window manager: " << e.what() << std::endl; + } } MWWorld::Ptr World::getFacedObject(float maxDistance, bool ignorePlayer) @@ -2206,7 +2207,7 @@ namespace MWWorld { const ESM::NPC *player = mStore.get().find("player"); if (!mPlayer) - mPlayer = new MWWorld::Player(player); + mPlayer.reset(new MWWorld::Player(player)); else { // Remove the old CharacterController @@ -2451,6 +2452,10 @@ namespace MWWorld if (ptr.getRefData().isDeleted()) return true; + // vanilla Morrowind does not allow to sell items from containers with zero capacity + if (ptr.getClass().getCapacity(ptr) <= 0.f) + return true; + if (Misc::StringUtils::ciEqual(ptr.getCellRef().getOwner(), mOwner.getCellRef().getRefId())) mOut.push_back(ptr); @@ -3471,17 +3476,17 @@ namespace MWWorld if (MWMechanics::isSummoningEffect(it->mEffectID)) { - preload(mWorldScene, mStore, "VFX_Summon_Start"); - preload(mWorldScene, mStore, MWMechanics::getSummonedCreature(it->mEffectID)); + preload(mWorldScene.get(), mStore, "VFX_Summon_Start"); + preload(mWorldScene.get(), mStore, MWMechanics::getSummonedCreature(it->mEffectID)); } - preload(mWorldScene, mStore, effect->mCasting); - preload(mWorldScene, mStore, effect->mHit); + preload(mWorldScene.get(), mStore, effect->mCasting); + preload(mWorldScene.get(), mStore, effect->mHit); if (it->mArea > 0) - preload(mWorldScene, mStore, effect->mArea); + preload(mWorldScene.get(), mStore, effect->mArea); if (it->mRange == ESM::RT_Target) - preload(mWorldScene, mStore, effect->mBolt); + preload(mWorldScene.get(), mStore, effect->mBolt); } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index e432e77d3..17e103290 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -74,17 +74,11 @@ namespace MWWorld Resource::ResourceSystem* mResourceSystem; Fallback::Map mFallback; - MWRender::RenderingManager* mRendering; - MWWorld::WeatherManager* mWeatherManager; - - MWWorld::Scene *mWorldScene; - MWWorld::Player *mPlayer; std::vector mEsm; MWWorld::ESMStore mStore; LocalScripts mLocalScripts; MWWorld::Globals mGlobalVariables; - MWPhysics::PhysicsSystem *mPhysics; bool mSky; ESM::Variant* mGameHour; @@ -98,6 +92,11 @@ namespace MWWorld std::string mCurrentWorldSpace; + std::unique_ptr mPlayer; + std::unique_ptr mPhysics; + std::unique_ptr mRendering; + std::unique_ptr mWorldScene; + std::unique_ptr mWeatherManager; std::shared_ptr mProjectileManager; bool mGodMode; diff --git a/apps/wizard/installationtargetpage.cpp b/apps/wizard/installationtargetpage.cpp index 3a179a2cb..f55794cca 100644 --- a/apps/wizard/installationtargetpage.cpp +++ b/apps/wizard/installationtargetpage.cpp @@ -20,7 +20,7 @@ Wizard::InstallationTargetPage::InstallationTargetPage(QWidget *parent, const Fi void Wizard::InstallationTargetPage::initializePage() { QString path(QFile::decodeName(mCfgMgr.getUserDataPath().string().c_str())); - path.append(QDir::separator() + QLatin1String("data")); + path.append(QDir::separator() + QLatin1String("basedata")); QDir dir(path); targetLineEdit->setText(QDir::toNativeSeparators(dir.absolutePath())); diff --git a/cmake/FindSDL2.cmake b/cmake/FindSDL2.cmake index 25105f1e2..4f2be8c42 100644 --- a/cmake/FindSDL2.cmake +++ b/cmake/FindSDL2.cmake @@ -70,10 +70,10 @@ endif() libfind_pkg_detect(SDL2 sdl2 FIND_PATH SDL.h HINTS $ENV{SDL2DIR} - PATH_SUFFIXES include SDL2 + PATH_SUFFIXES include SDL2 include/SDL2 FIND_LIBRARY SDL2 HINTS $ENV{SDL2DIR} - PATH_SUFFIXES ${_sdl_lib_suffix} + PATH_SUFFIXES lib ${_sdl_lib_suffix} ) libfind_version_n_header(SDL2 NAMES SDL_version.h DEFINES SDL_MAJOR_VERSION SDL_MINOR_VERSION SDL_PATCHLEVEL) @@ -85,7 +85,7 @@ IF(NOT SDL2_BUILDING_LIBRARY AND NOT APPLE) libfind_pkg_detect(SDL2MAIN sdl2 FIND_LIBRARY SDL2main HINTS $ENV{SDL2DIR} - PATH_SUFFIXES ${_sdl_lib_suffix} + PATH_SUFFIXES lib ${_sdl_lib_suffix} ) set(SDL2MAIN_FIND_QUIETLY TRUE) libfind_process(SDL2MAIN) diff --git a/components/compiler/fileparser.cpp b/components/compiler/fileparser.cpp index 8711d92fe..52a9a63f1 100644 --- a/components/compiler/fileparser.cpp +++ b/components/compiler/fileparser.cpp @@ -63,6 +63,7 @@ namespace Compiler if (mState==BeginState && keyword==Scanner::K_begin) { mState = NameState; + scanner.enableTolerantNames(); /// \todo disable return true; } diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index c2ec13b01..bb0fb9374 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -27,6 +27,7 @@ namespace Compiler if (c=='\n') { mStrictKeywords = false; + mTolerantNames = false; mLoc.mColumn = 0; ++mLoc.mLine; mLoc.mLiteral.clear(); @@ -363,7 +364,7 @@ namespace Compiler } else if (!(c=='"' && name.empty())) { - if (!isStringCharacter (c)) + if (!isStringCharacter (c) && !(mTolerantNames && (c=='.' || c=='-'))) { putback (c); break; @@ -577,7 +578,7 @@ namespace Compiler const Extensions *extensions) : mErrorHandler (errorHandler), mStream (inputStream), mExtensions (extensions), mPutback (Putback_None), mPutbackCode(0), mPutbackInteger(0), mPutbackFloat(0), - mStrictKeywords (false) + mStrictKeywords (false), mTolerantNames (false) { } @@ -634,4 +635,9 @@ namespace Compiler { mStrictKeywords = true; } + + void Scanner::enableTolerantNames() + { + mTolerantNames = true; + } } diff --git a/components/compiler/scanner.hpp b/components/compiler/scanner.hpp index 270782c74..49fbaa96a 100644 --- a/components/compiler/scanner.hpp +++ b/components/compiler/scanner.hpp @@ -38,6 +38,7 @@ namespace Compiler std::string mPutbackName; TokenLoc mPutbackLoc; bool mStrictKeywords; + bool mTolerantNames; public: @@ -129,6 +130,11 @@ namespace Compiler /// /// \attention This mode lasts only until the next newline is reached. void enableStrictKeywords(); + + /// Continue parsing a name when hitting a '.' or a '-' + /// + /// \attention This mode lasts only until the next newline is reached. + void enableTolerantNames(); }; } diff --git a/components/esm/loadtes3.hpp b/components/esm/loadtes3.hpp index f2dde4d1f..896936cf5 100644 --- a/components/esm/loadtes3.hpp +++ b/components/esm/loadtes3.hpp @@ -13,24 +13,37 @@ namespace ESM #pragma pack(push) #pragma pack(1) + struct Data + { + /* File format version. This is actually a float, the supported + versions are 1.2 and 1.3. These correspond to: + 1.2 = 0x3f99999a and 1.3 = 0x3fa66666 + */ + unsigned int version; + int type; // 0=esp, 1=esm, 32=ess (unused) + NAME32 author; // Author's name + NAME256 desc; // File description + int records; // Number of records + }; + + struct GMDT + { + float mCurrentHealth; + float mMaximumHealth; + float mHour; + unsigned char unknown1[12]; + NAME64 mCurrentCell; + unsigned char unknown2[4]; + NAME32 mPlayerName; + }; + +#pragma pack(pop) + /// \brief File header record struct Header { static const int CurrentFormat = 0; // most recent known format - struct Data - { - /* File format version. This is actually a float, the supported - versions are 1.2 and 1.3. These correspond to: - 1.2 = 0x3f99999a and 1.3 = 0x3fa66666 - */ - unsigned int version; - int type; // 0=esp, 1=esm, 32=ess (unused) - NAME32 author; // Author's name - NAME256 desc; // File description - int records; // Number of records - }; - // Defines another files (esm or esp) that this file depends upon. struct MasterData { @@ -39,16 +52,6 @@ namespace ESM int index; // Position of the parent file in the global list of loaded files }; - struct GMDT - { - float mCurrentHealth; - float mMaximumHealth; - float mHour; - unsigned char unknown1[12]; - NAME64 mCurrentCell; - unsigned char unknown2[4]; - NAME32 mPlayerName; - }; GMDT mGameData; // Used in .ess savegames only std::vector mSCRD; // Used in .ess savegames only, unknown std::vector mSCRS; // Used in .ess savegames only, screenshot @@ -62,7 +65,6 @@ namespace ESM void load (ESMReader &esm); void save (ESMWriter &esm); }; -#pragma pack(pop) } diff --git a/components/files/escape.cpp b/components/files/escape.cpp index f28870c70..93ae9b885 100644 --- a/components/files/escape.cpp +++ b/components/files/escape.cpp @@ -1,6 +1,6 @@ #include "escape.hpp" -#include +#include namespace Files { @@ -8,7 +8,7 @@ namespace Files const int escape_hash_filter::sEscapeIdentifier = 'a'; const int escape_hash_filter::sHashIdentifier = 'h'; - escape_hash_filter::escape_hash_filter() : mNext(), mPrevious(), mSeenNonWhitespace(false), mFinishLine(false) + escape_hash_filter::escape_hash_filter() : mSeenNonWhitespace(false), mFinishLine(false) { } @@ -26,8 +26,14 @@ namespace Files std::string EscapeHashString::processString(const std::string & str) { - std::string temp = boost::replace_all_copy(str, std::string() + (char)escape_hash_filter::sEscape + (char)escape_hash_filter::sHashIdentifier, "#"); - boost::replace_all(temp, std::string() + (char)escape_hash_filter::sEscape + (char)escape_hash_filter::sEscapeIdentifier, std::string((char)escape_hash_filter::sEscape, 1)); + std::string temp = str; + + static const char hash[] = { escape_hash_filter::sEscape, escape_hash_filter::sHashIdentifier }; + Misc::StringUtils::replaceAll(temp, hash, "#", 2, 1); + + static const char escape[] = { escape_hash_filter::sEscape, escape_hash_filter::sEscapeIdentifier }; + Misc::StringUtils::replaceAll(temp, escape, "@", 2, 1); + return temp; } diff --git a/components/files/escape.hpp b/components/files/escape.hpp index 2017c2ed2..d01bd8d98 100644 --- a/components/files/escape.hpp +++ b/components/files/escape.hpp @@ -30,7 +30,6 @@ namespace Files private: std::queue mNext; - int mPrevious; bool mSeenNonWhitespace; bool mFinishLine; @@ -42,11 +41,9 @@ namespace Files if (mNext.empty()) { int character = boost::iostreams::get(src); - bool record = true; if (character == boost::iostreams::WOULD_BLOCK) { mNext.push(character); - record = false; } else if (character == EOF) { @@ -78,7 +75,7 @@ namespace Files mFinishLine = true; } } - else if (mPrevious == sEscape) + else if (character == sEscape) { mNext.push(sEscape); mNext.push(sEscapeIdentifier); @@ -89,8 +86,6 @@ namespace Files } if (!mSeenNonWhitespace && !isspace(character)) mSeenNonWhitespace = true; - if (record) - mPrevious = character; } int retval = mNext.front(); mNext.pop(); diff --git a/components/misc/stringops.hpp b/components/misc/stringops.hpp index 9acd81710..9f4931d72 100644 --- a/components/misc/stringops.hpp +++ b/components/misc/stringops.hpp @@ -2,6 +2,7 @@ #define MISC_STRINGOPS_H #include +#include #include #include @@ -138,6 +139,35 @@ public: return notFound; } + + /** @brief Replaces all occurrences of a string in another string. + * + * @param str The string to operate on. + * @param what The string to replace. + * @param with The replacement string. + * @param whatLen The length of the string to replace. + * @param withLen The length of the replacement string. + * + * @return A reference to the string passed in @p str. + */ + static std::string &replaceAll(std::string &str, const char *what, const char *with, + std::size_t whatLen=std::string::npos, std::size_t withLen=std::string::npos) + { + if (whatLen == std::string::npos) + whatLen = strlen(what); + + if (withLen == std::string::npos) + withLen = strlen(with); + + std::size_t found; + std::size_t offset = 0; + while((found = str.find(what, offset, whatLen)) != std::string::npos) + { + str.replace(found, whatLen, with, withLen); + offset = found + withLen; + } + return str; + } }; } diff --git a/components/misc/utf8stream.hpp b/components/misc/utf8stream.hpp index 760015902..368374a64 100644 --- a/components/misc/utf8stream.hpp +++ b/components/misc/utf8stream.hpp @@ -18,6 +18,11 @@ public: { } + Utf8Stream (const char * str) : + cur ((unsigned char*) str), nxt ((unsigned char*) str), end ((unsigned char*) str + strlen(str)), val(Utf8Stream::sBadChar()) + { + } + Utf8Stream (std::pair range) : cur (range.first), nxt (range.first), end (range.second), val(Utf8Stream::sBadChar()) { diff --git a/components/nif/data.cpp b/components/nif/data.cpp index a6721fde1..d19c8321e 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -153,12 +153,12 @@ void NiPixelData::read(NIFStream *nif) // Unknown nif->skip(12); - mips = nif->getInt(); + numberOfMipmaps = nif->getInt(); // Bytes per pixel, should be bpp * 8 /* int bytes = */ nif->getInt(); - for(int i=0; i